はじめに
JavaScriptの興味ネタの続きになります。
JavaScriptを利用する時に「スクリプト」をどこに書くのか?
他のJavaScriptとの順番は?「defer?」、「async?」ナニコレ?つけるの?つけないの?
を迷うことがあったので、改めて整理しようかと思います。
<script src="fileA.js"></script>
参考サイト
迷いポイントその1
「defer?」、「async?」付ける必要があるの?
その前に、付けていないとどういう動きになる?
ブラウザは、サーバよりHTMLを受領すると上から順に解析していく。
その過程で「スクリプト」を発見すると、ダウンロード・解析・実行する。
※ MDNの「メモ」の「読み込み」をダウンロード・解析と推測。
気を付けることが「スクリプト」で、HTMLを操作する処理があると、HTMLの解析がまだ終わっていないからエラーになる。
【サンプル】スクリプトで、HTML(ボタン)の表示名を更新
<!DOCTYPE html>
<html>
<head>
<script>
// ❌ この時点ではまだ <body> 内の要素は解析されていない
const btn = document.getElementById('myButton');
btn.textContent = '更新!'; // btnがnullなのでエラー(TypeError)
</script>
</head>
<body>
<button id="myButton">ボタン</button>
</body>
</html>
スクリプトを書く位置をボタンの後にすると、解消される。
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<button id="myButton">ボタン</button>
<script>
const btn = document.getElementById('myButton');
btn.textContent = '更新!'; // 正常に動作する
</script>
</body>
</html>
上から順に解析していく中で、スクリプトを発見し、解析・実行!しても、ボタンを読み込んでいないからエラーになる。
で、スクリプトをボタンの後にすると、動く納得。
とはいえ、書く位置に依存しているとバグりやすいので、他の対処法として「defer」・「DOMContentLoaded」もあるとのこと。(From Gemini)
deferとは?
defer属性を付与すると、実行が「DOMContentLoaded」イベント発生前になるとのこと。
DOMContentLoaded とは?
「HTMLの文書」が解析されてから発生するイベント。
先ほどの例だと、ボタンも読み込まれてからかな。
<!DOCTYPE html>
<html>
<head>
<script>
document.addEventListener('DOMContentLoaded', () => {
const btn = document.getElementById('myButton');
btn.textContent = '更新!';
});
</script>
</head>
<body>
<button id="myButton">ボタン</button>
</body>
</html>
話を戻すと
エラーになっていたスクリプトタグに「defer」を付けてみると?
<!DOCTYPE html>
<html>
<head>
<script defer>
// ❌ この時点ではまだ <body> 内の要素は解析されていない
const btn = document.getElementById('myButton');
btn.textContent = '更新!'; // btnがnullなのでエラー(TypeError)
</script>
</head>
<body>
<button id="myButton">ボタン</button>
</body>
</html>
変わらず、エラー。
あ、なるほど。src属性のJavaScriptにする必要があるとのこと。
src属性にしてdeferを付けると、エラーにならない!
<!DOCTYPE html>
<html>
<head>
<script src="sample4.js" defer></script>
</head>
<body>
<button id="myButton">ボタン</button>
</body>
</html>
const btn = document.getElementById('myButton');
btn.textContent = '更新!';
改めてdefer属性とは?
「defer」属性とは、スクリプトの実行が「DOMContentLoaded」のイベント発生前=HTMLの「文書」が完全に読み込まれてから?動くようになるのかな?
利用する際は、HTMLの文書を操作する処理がある場合は、付けておくのがよき。
とはいえ、上記観点のみであれば、スクリプトを作った人 と スクリプトを利用する人が異なると、利用する側にお任せとなるので、スクリプト内で「DOMContentLoaded」を利用し制御したほうが安全かな?
と思う。
もう少し、defer属性のメリットを深堀すると、
とあるので、Gemini解説と自身の推測から、おそらく、「読み込み前の処理」=「ダウンロード」を並列で行ってくれて、HTMLの解析を止める(= パーサーブロッキング Javscript)のを避けてくれるのかな?と。
そうすると、画面描画までの時間が短くなるメリットを理解できる。
であるなら、
スクリプトを作成する側は、HTMLの文書に依存する処理を「DOMContentLoaded」を利用し考慮。
スクリプトを利用する側は、画面のパフォーマンスを考慮し「defer」を付ける。
という棲み分けは理解できる。
とすると、基本「defer」は付けて良い気がする。
asyncとは?
defer属性と同じく、ダウンロードは並行で実施してくれる雰囲気。
実行は、defer属性とは異なり、利用可能(=ダウンロード完了?)になると直ぐに実行する感じかな?
defer属性は、HTML文書が読み込まれてから実行。
async属性は、利用可能(=ダウンロード完了?)したら実行。
と考えると、HTML文書に依存しないスクリプト かつ 早く実行したいスクリプトであれば、defer属性より、async属性のほうがよいのかな?
迷いポイントその2
「スクリプト」を書く位置ってどこが良いの?
見かけるのが、ヘッダ、ボディの末尾とか。
ヘッダ
<!DOCTYPE html>
<html>
<head>
<script src="sample4.js"></script>
</head>
<body>
<button id="myButton">ボタン</button>
</body>
</html>
ボディの末尾
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<button id="myButton">ボタン</button>
<script src="sample4.js"></script>
</body>
</html>
ちゃんと考えてみると。
HTMLは上から順に読み込まれる。
defer、asyncが無いスクリプトだと、読み込まれた時点でHTMLの解析を止めて、ダウンロード、解析、実行される。
動作面で考えると
HTMLに依存しない処理であればどっちでも良い。
性能面を考えると
HTMLの解析とJavaScriptのダウンロードを並行して実施したほうがよいので、defer属性 OR async属性 を付ける。
で、早めにJavaScriptのダウンロードをさせたいので、ボディの末尾よりヘッダのほうが良い雰囲気。
まとめ
async、deferは、スクリプトのダウンロードを、HTMLの解析と並行して行うようにしてくれる。
async、deferの違いは、実行タイミングが異なる。
async:スクリプトのダウンロードが終わったら
defer:HTML文書を読み込んでから
どちらも付けていない場合は、スクリプトを発見すると、HTMLの解析を止めて、ダウンロード、解析、実行をする。
このため、ヘッダにスクリプト次第でasync OR deferを付けて定義するのが良い雰囲気。
が、そこまでパフォーマンスを意識しなくてよければ、スクリプト次第でヘッダかボディの末尾
かな。
って感じかな。






