初めに
HTMLの<script>
要素にはdefer
属性がある。これを使うことで、HTML・JavaScript両方のコーディングが改善されるので、開発者はもっと取り入れるべきだ。
defer
のブラウザ対応は非常に良い、というかすべての現行ブラウザで利用可能なので、使わない手は無い。
利点その1:DOMを読み込む<script>
でも<head>
内に置ける!
よくある<script>
の書き方として、<body>
の末尾に置くというプラクティスが良く知られている。WebページのDOMを操作するメソッドなどでよく使われる手法だ。しかし、これでは<head>
にある先読みしておきたい<script>
と場所が分散されてしまうので、HTMLの見通しが悪くなるという欠点がある。それにただのスクリプトは文書で遭遇した際に逐次読込が発生するのでローディングタイムが遅くなってしまう。
それを解決してくれるのがdefer
だ。これは<script>
を「文書の解析完了後かつ DOMContentLoaded が発生する前に実行することをブラウザーに示」すものである。つまり、<head>
に置いた<script>
でも、文書構造が分かった状態で読み込めることになる。それもただ文章構造が読み込まれていることを期待して<script>
を<body>
末尾に置くのとは違い、仕様で確約されている。実際の読み込みタイミングは「<script> タグに async / defer を付けた場合のタイミング」を読んで欲しい。
なお、<script type="module">
の読み込みも基本的にはdefer
スクリプトと同じタイミングである。というより import
や export
を使うことで、読み込み順の問題を回避できるし、旧来のJavaScriptの書き方でカプセル化ができるようになるので、これからは書き捨てのテストスクリプトをHTMLファイルに書く場合(残念ながら import
出来ない。何故だ?1)以外は module
に移行すべきである。
なお、defer
スクリプト同士の実行順はソース順のままなので、別に今まで通り<body>
末尾に置いてもいい。移行の初期段階はそうすることだろう。HTMLをリファクタリングする段階で、<head>
に移せばいい。
DOMに関係ない処理(特に早めに済ませたいもの)はasync
、そうでないものはdefer
と住み分けるのが重要だ。我々はDIOではないので、時、もといレンダリング処理を止めてまで処理を進める必要は皆無だ。
利点その2:イベントリスナを設定しなくてよい
<body>
末尾法の場合、スクリプトを動かす際にdocument.addEventListener("DOMContentLoaded", doSomething)
(一般的には"load"
の方を使うかな。あんまり良いことでないけど)という風にイベントを設定する必要がある。
しかし、defer
スクリプトはそんなことをせずにトップレベルでスクリプトを書ける(というよりむしろ、document.addEventListener("DOMContentLoaded", doSomething)
とすると動作しなくなる)。
// こういうふうにスクリプトに直に書かれた関数が、deferスクリプト実行時に読み出される。
// しかもそもそもHTMLに<p>要素が無い場合を除いて、見つからないという例外が発生しない!
const paragraphs = document.getElementsByTagName("p");
これで関数を書くのが飛躍的に楽になるので、defer
の存在を知ってからは私は<script>
に必ずdefer
を付けることにしている(async
が必要な関数を書かないので…)。
これでいて、デメリットは特に存在しない。本当に使わなきゃ損!
-
つまりこれからはJSは別ファイルに分けましょうねということなのに、いちいち閉じタグを付けなければならないので冗長極まりない。CSSはインラインは
<style>
要素で記述し、外部ファイル読み出しは<link>
要素で読み込むのに、それが無い。そもそも本来はスクリプトの種類を表すはずだったtype
属性をJS仕様の設定に使うなど、拡張性が無いのは実におかしい。自分は本来Java用だったJVMという土台を用いるScalaやKotlinのように、HTTPというプロトコルを使いつつもJSにおけるvar
のような問題ある文法やfor (let a in b)
という誰得機能などの歴史的経緯の頸木から外れた新しいフォーマットを作るべきだと本気で思っている。 ↩