ディップ Advent Calendar 2017 の5日目です。
JavaScriptはその処理内容自体もパフォーマンスチューニングの対象になりますが、HTML側での<script>
タグの書き方も大事です。
レンダリング前のプロセス
ブラウザが描画の前にやっていること
ブラウザはHTMLをサーバーからダウンロードすると、その内容をパース(解析)してDOMツリー(文書構造の内部表現)を構築する。
<script>タグによるパースの中断
<script src="foo.js"></script>
HTMLパーサーは、パースの途中で<script>
タグがあるとそのスクリプトを実行し終わるまで一時的にパースを中断する。
src属性がある場合(外部JavaScript)はサーバーにリクエストし、ダウンロードして実行するので、その分中断時間が長くなる。
<script>
タグを</body>
の前に記述するのがセオリーなのはこのため。
なぜパースが中断されるのか
JavaScriptのdocument.write()
メソッドは、その場にHTMLを書き出す機能を持つ。
そのためこのメソッドが実行されると、パースの結果生成されるDOMツリーの構造に影響を与えてしまう。
スクリプト内にdocument.write()
メソッドが使われているかどうかはスクリプトの内容を見なければ分からないため、<script>
タグの位置でパースを一時的に中断して、スクリプトの内容を取得・解析する。
<script>要素のdefer属性・async属性
<script src="foo.js" defer></script>
<script src="bar.js" async></script>
src属性を持つ<script>
要素にdefer属性またはasync属性を付与することにより、パースが中断されなくなる。
その代わり、そのスクリプト内に記述されたdocument.write()
メソッドは無視されるようになる。
そのスクリプト内でdocument.write()
メソッドが実行されない事が保証されることで、HTMLパーサーはパースを続行できる。
defer属性とasync属性の使い分け
defer属性
スクリプトが<script>
要素の順に実行されることが保証される。
HTMLのパースは中断されないが、実行されるのはDOMツリー構築後であることに注意。
外部のJSライブラリ(jQueryとか)の機能を使ったJSファイルやプラグイン等、スクリプト間に依存関係がある場合にはこちらを指定する。
DOMツリー構築完了後、常にscript1 → script2の順に実行される
<script src="script1.js" defer></script>
<script src="script2.js" defer></script>
async属性
スクリプトが<script>
要素の順に実行されることが保証されない。
スクリプトのダウンロードが終わると直ちにコンパイル&実行されるため、スクリプト間の依存関係が無い場合はこちらの方が高速。
ダウンロードが完了した順に実行される
<script src="script1.js" async></script>
<script src="script2.js" async></script>
それでは、残り僅かになった酉年を大切にお過ごしください!