以下のコードを実行するとコンソールには何が表示されるでしょうか?
<head>
<script src="index.js"></script>
</head>
<body>
<h1 id="test">script実行</h1>
</body>
const test = document.getElementById('test');
console.log(test);
結果はnull
です。
DOM構築はHTMLの記述順に行われます。
その間にscriptタグを見つけるとDOM構築を一旦停止し、設定されたJavaScriptファイルのダウンロードと実行を行います。
JavaScriptの実行後にDOM構築を再開します。
つまり上記のコードでは、index.jsを実行した時点で、まだid=“test”
のh1タグは構築されていないため取得ができずnull
になる、ということです。
また、DOM構築を停止するということは、利用者はその間コンテンツを見ることができません。
今回はこれを回避するscriptタグの書き方を紹介します。
同期的読み込み(bodyタグ内)
<body>
<h1 id="test">script実行</h1>
<script src="index.js"></script>
</body>
headタグ内ではなく、</body>
の直前に記述する方法です。
DOM構築が終わったあとでJavaScriptのダウンロードと実行が行われるため、要素の取得ができます。
ただ、全てのDOM構築が完了してからJavaScriptのダウンロードを開始するため、実行までに時間がかかるかもしれません。
表示速度も考慮するなら次の非同期的読み込みが有効です。
非同期的読み込み(async属性)
<head>
<script src="index.js" async></script>
</head>
scriptタグにasync属性を付与する方法です。
async属性を付与すると、非同期にJavaScriptファイルのダウンロードと実行をします。
つまりDOM構築を停止せずに、並行してダウンロードします。
実行のタイミングはダウンロード完了後すぐです。
そのため、async属性付きのscriptタグが複数あった場合、scriptタグの記述順に実行されるわけではありません。
非同期的読み込み(defer属性)
<head>
<script src="index.js" defer></script>
</head>
scriptタグにdefer属性を付与する方法です。
async属性と同じく非同期にJavaScriptファイルのダウンロードと実行をするため、DOM構築を停止しません。
async属性との違いは以下が挙げられます。
- 実行タイミングはDOMContentLoadedイベントの直前(DOM構築が完了したとき)
- defer属性付きのscriptタグが複数あった場合、scriptタグの記述順に実行される
まとめ
以上の書き方であれば、意図通りに要素の取得ができます。
ただ、表示速度のことを考慮するとDOM構築と並行してJavaScriptのダウンロードができる非同期的読み込みが良さそうです。
非同期的読み込みのasync属性とdefer属性の違いとして、実行タイミングが挙げられます。
async属性の場合、ダウンロード後すぐに実行されます。
一方defer属性の場合、必ずDOM構築完了後、scriptタグの記述順に実行されるため扱いやすいと思います。