初めに
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)という誰得機能などの歴史的経緯の頸木から外れた新しいフォーマットを作るべきだと本気で思っている。 ↩