JavaScriptのコードをHTMLとは別ファイルとして保存し、HTMLからリンクさせて読み込むことを「外部ファイル化する」と呼んだりします。
その際、以下のようにHTMLのhead要素内から読み込むことには大きなデメリットが実はあるのです。
<html>
<head>
<script src="ここにJavaScriptファイルへのパス"></script>
</head>
<body>
</body>
</html>
このページではそのデメリットと理由や解決法について解説していきます。
JavaScriptの外部ファイルをhead要素内で読み込むとどうなるか?
Webブラウザがページを画面上に描画するためには、まずHTMLを読み込んで解析する必要があります。
この解析機能のことは「パーサー」と呼ばれたりします。
パーサーはHTMLファイルの一番上から順番に解析していき、その途中に外部のJavaScriptファイルがあれば、その時点でそのJSファイルをダウンロードしに行きます。
そして、外部JSファイルをダウンロード・実行されるまでの間はそれ以降のHTMLの解析がストップさせられるため、head要素内にそのような外部ファイルがあると結果的にページが表示されるのがそのぶん遅くなります。
最近のPCやスマホの性能の高さから考えると、そのJSの実行時間よりもHTTP通信が1本(外部ファイルが1つの場合)発生することによる遅延のほうが大きいと思われます。
外部JavaScriptファイルはbody要素の最下部から読み込むほうがベター
JavaScriptの外部ファイルの読み込みコードはhead要素内に限らずbody要素内に書くことも認められています。
<html>
<head>
</head>
<body>
<script src="ここにJavaScriptファイルへのパス"></script>
</body>
</html>
上記のようにbody要素の最後から外部のJavaScriptファイルをダウンロードすることで、HTMLの解析がストップされることなく最後(外部JSのコードがある位置)までいきます。
HTMLの解析が最後まで行くということは、
- そこまでのHTMLのDOM構築は完了し、構築できている分は画面上に表示されている。(構築処理にかかる時間は微微たるものなので今回は考慮しません)
- そこまでのHTMLにCSSファイルや画像ファイル(img要素)の読み込みコードがあれば、ダウンロードを開始できる。
ということになります。
これがもしhead要素内でパース(解析)がストップした場合は、body要素内のDOM構築は一切できておらず、画像ファイルのダウンロードも開始されません。
【補足】
実際には、現在のブラウザには「プリロードスキャナー」という仕組みが実装されているので、パース(解析)が止められてもそれ以降にあるCSSやJS、画像のダウンロードだけは先読みして開始されます。しかし、DOMの構築はパースが再開されるまでされません。
一般的なページ構造であれば、ページを表示するために最も優先されるべきものはHTMLファイルとCSSファイルですから、そちらをなるべく速い段階からダウンロード・解析させて、一瞬でも速くDOMやCSSOMを構築させたほうがページ表示のタイミングが速くなります。
【補足】
ブラウザがページを画面上に表示するためには、HTMLを読み込んで「DOMツリー」を、CSSを読み込んで「CSSOMツリー」というものを内部的に構築する必要があります。
たとえトータルでダウンロードするファイルの数やサイズは同じだとしても、画面上に表示されるタイミングが一瞬でも速いほうが体感速度が速まるのです。
そのような理由から、ページの表示後にダウンロード・実行しても問題ない内容(アクセス解析ツールなど)のJavaScriptファイルはhead要素内で読み込むのではなく、body要素の最下部に書いておいたほうが良いでしょう。
関連ページ:パーサーブロックとレンダリングブロックの違いについて解説
JavaScriptをhead要素内で読み込んだとしても、その実行タイミングはページ表示後だったりします
実際のところ、head要素内でJavaScriptコードを読み込んだとしても、そのコード内容が実行されるタイミングはページ表示後というケースが多いです。
なぜなら、JavaScriptのコードではreadyやonloadといった関数が使用されるのが多く、これらの関数によって実行タイミングが「DOMツリー構築後(ready,domContentLoaded)」とか「ページの表示後(onload)」などにされているからです。
「DOMツリー構築後って何だ?」と思った方もいると思いますが、ここでは大ざっぱに「ページ表示後とだいたい同じ」と考えて良いです。
つまり、head要素内で外部JavaScriptファイルを読み込むと、
外部JavaScriptファイルをダウンロードしている間はそれ以降のHTML解析が止められるけども、そのJavaScriptの内容の実行はページ表示後に行われる。
という全く無駄な状況が生まれてしまうのです。
というわけで、外部JavaScriptファイルの読み込みコードはbody最下部に書くようにしましょう。
ページ表示速度にうるさいGoogleもそれを推奨しています。
参考リンク:レンダリングを妨げる JavaScript を削除する | PageSpeed Insights | Google Developers
WordPressでjQueryをフッターで読み込ませたい場合は「WordPress同梱のjQueryをGoogle CDNのものに変更してwp_footer()で読み込む方法」のページで解説しています。
さらに追記:外部JSファイルの読み込み時に使うdeferとasync属性について
外部JSファイルを読み込むHTMLコードは通常は以下のようになりますが、
<script src="ここにJavaScriptファイルへのパス"></script>
以下のようにdeferやasyncを付けておくことで、HTMLの解析をストップさせることなく、このJSをダウンロード・実行させることができます。
この場合はhead要素内に以下のコードを書いたとしても問題ないでしょう。
<script src="ここにJavaScriptファイルへのパス" defer></script>
<script src="ここにJavaScriptファイルへのパス" async></script>
deferとasyncはそれぞれ一長一短があります。JSの内容によって使い分けるのがベストでしょう。
がしかし、deferとasyncはその内容の実行タイミングが異なるため、よくわからない場合や正常動作の検証ができない人は全部deferにしておくほうが無難です。
「jQueryのような依存関係のあるファイルが存在しない」とか「DOMを操作しない」ような内容のJSファイルであれば、asyncでも問題ないと思います。