【HTML】css・jsは何故ここで読み込むのか?

〇疑問

HTMLを記述すると、下記のように外部ソースをほぼ必ず読み込むことなる。

<head>
<link type="text/css" rel="stylesheet" href="./css/iblis1.css">
<link type="text/css" rel="stylesheet" href="./css/iblis2.css">
<script type="text/javascript" src="./js/iblis1.js"></script>
</head>

<body>
~
<script type="text/javascript" src="./js/iblis2.js"></script>
</body>

cssは<head>先頭付近、javascriptは<head>末尾付近か<body>末尾付近に記述されていることが多い。
いったいなぜ、このような位置に記述されているのだろうか?と気になって調べてみたところ、どうやら読み込みの処理と順序の特徴が関わっているようだった。

〇答え

・cssはHTML解析と同時進行でcssの読み込み(ダウンロード)を行えるので、HTMLの中でも最初の方にある<head>先頭付近に記述すると解析と読込の並列処理で効率的。
・javascriptはHTML解析と同時進行でjsの読み込みを行わないので、<body>末尾付近に記述することでHTML解析に遅延を発生させないようにしている。
・HTML解析を遅らせてでもjavascriptを<head>末尾付近に記述するのは<body>内でjavascript内の関数を呼び出す必要があるなど、やむを得ない場合のとき。

〇結論:どこに書くのがいいのか?

cssは<head>先頭付近に記述し、HTML解析の始めに並列して読み込むのがよい。
javascriptはdefer属性を指定し、HTML解析と並列して読み込むようにした上で<head>末尾付近に記述するのがよい。

①javascriptの外部ファイルを読み込む際、ブラウザのHTML解析が一時中断される。

ブラウザーはふつう、最も悪いシナリオを想定し、 HTML の解釈中は同期的にスクリプトを読み込みます (つまり、 async="false" です)。
出典:MDN スクリプト要素(async)

ブラウザのパーサーと呼ばれるHTMLの解析機能は、最悪のシナリオを想定しHTML解析中は同期的(直列的)にjsを読み込みするようになっている。
その為、パーサーに対してHTMLの段階で読み込み方法の要求を記述しない限りは1つ1つ丁寧に読み込んで解析してしまい、その間のHTML解析が中断される。
<body>末尾付近に記述されている理由は、HTML解析の大部分が完了した後にjsを読み込むことで、HTML解析に遅延を発生させない為のようだ。

②cssの外部ファイルを読み込む際、ブラウザは並行してHTML解析もできる。

jsは一つ一つ丁寧に読み込んでいるのにcssはなぜ並行して読み込めるのか?
この疑問に対して答えてくれる記事は見つからなかった。
そのため推測になってしまうが、cssは記述されているプロパティをただ適用していくだけのものなので、jsと違い実行順序によって結果が変わることが無い為だと思われる。
読み込み自体は順番が狂ったとしても、HTML上の記述順に上から正しく並び替えて適用すればよいのだ。
そうしてHTML解析と並行にcssの読み込み処理を実行することができるので、<head>先頭付近に書いておくことで解析と同時進行で読み込んで時間を短縮しているようだ。

③javascriptは処理の順序が大事な場合もある。

もし、jsを全て<body>末尾付近で読み込んでいると、<body>内のどこかでjsの関数が参照されている場合に存在しない関数を呼び出してしまうことになる。
そうなると実行されるべき関数が実行されない結果となり、動くべき部品が動かずユーザは混乱してしまう。
それを避けるために、jQuery等の関数の集合体のようなjsは<head>末尾付近に記述することで<body>タグより先に読み込んでおき、<body>内で使われても問題が起きないようにしているようだ。

④実は、javascriptも全部<head>タグ内で読み込んでいいのではないか?

基本的にブラウザは最悪のシナリオを想定し、HTMLの解析とjavascriptを同時進行せずに順番に丁寧に処理をする。
しかし、asyncやdeferという属性を設定してブラウザに対して読み込み方法を明示すると同時進行で読み込んでくれるようになるようだ。
async属性の場合、jsを非同期に読み込んで実行する。要するに順序関係なく早くできそうなものからどんどん読み込んで実行する。
defer属性の場合、とりあえずjsを読み込んでおきHTML解析が終わった後に実行する。こちらは読み込んだ順番、つまりHTMLの記述順に上から実行する。

下記を参考に自分なりに動作イメージを作成した。
参考:Qiita <script> タグに async / defer を付けた場合のタイミング
参考:WHATWG の規格書

HTMLパース1

async及びdefer属性は共に、読み込みでHTML解析を止めることがない分、属性なしと比較して総実行時間が短くなる。
async属性はjs読込の完了後にjs実行してしまうためHTML解析完了までの時間も伸びてしまうが、defer属性の場合はHTML解析後に実行されるためHTML解析完了までの時間が短い。
<body>末尾付近にjsを記述した場合は読み込みもそこで行われる都合上、HTML解析完了までの時間はdefer属性を付けた時と同じ速さになるが、総実行時間は属性なしと大差ないイメージだ。
検証していないので実際の動作については不明だが、わざわざjsを<head>タグと<body>タグに分けて書くよりは<head>タグ内にまとめて記述する方が保守の面でも効率がよさそうなので、今後はdefer属性を使用してまとめようと思う。

⑤改善後のソース

最終的にこうなった。

<head>
<link type="text/css" rel="stylesheet" href="./css/iblis1.css">
<link type="text/css" rel="stylesheet" href="./css/iblis2.css">
<script type="text/javascript" src="./js/iblis1.js"></script>
<script type="text/javascript" src="./js/iblis2.js" defer></script>
</head>

<body>
~
</body>

もともと<head>末尾付近にあるjsは理由があってその位置であるはずなので、属性の指定はせずに1つ1つ丁寧に読込・実行したままにした。
<body>末尾付近にあったものはdefer属性を指定し、<head>末尾付近に元からあったjsのさらに下で読み込んでいる。
defer属性には現在のほとんどのブラウザが対応しているが、極端に古いブラウザでは対応していないことを考え、最低限の実行順序を守るためだ。
妙な疑問から思いがけない知識を拾い食いしてしまった。

〇おまけ:その他の参考

参考:Qiita 結局scriptをどこで読み込むのかが正解なのか
参考:[JS] JavaScriptの読み込み位置をページ最後にしたほうがよい理由
参考:JavaScript を最下段で読み込むのがあまり有効ではない理由
参考:Qiita script要素の新常識・安易にdefer/async属性を付けてはいけない

この記事が気に入ったらサポートをしてみませんか?