JavaScriptのスクリプトの非同期読み込み(async, defer)の基礎知識

2021年3月5日JavaScript

JavaScriptのスクリプトの非同期読み込み(async, defer)の基礎知識をまとめました。

JavaScriptの非同期読み込み(async, defer)の基礎知識

通常の書き方

通常の書き方は、bodyタグの閉じタグ直前にscriptタグを書くことで、HTMLでJavaScriptを読み込ませます。
その場合、HTMLがパースされた後、scriptのフェッチが始まり、それが終わるとscriptが実行されます。
もし、headに入れてしまうと、HTMLのその上に書かれた部分のみがパースされ、scriptのフェッチと実行が終わるまでメインのHTMLはパースされずに止まってしまいます。

asyncとdefer

理想としては、HTMLもJavaScriptも両方ともパースをして実行できることです。
その一部であるHTMLをパースすることとscriptをフェッチすることを同時並行して行えるのが、スクリプトの非同期読み込み(async, defer)です。
asyncとdeferのどちらもheadタグに入れることで、HTMLをパースすることとscriptをフェッチすることを同時並行して処理できます。

asyncとdeferの違い

asyncとdeferの違いは、scriptの実行のタイミングと順番にあります。
asyncの場合、scriptをフェッチした後即座にscriptが実行されます。
deferの場合、HTMLのパースが完全に終了した後に、scriptが実行されます。

なお、asyncとdeferはheadタグに入れるからこそ、HTMLのパースと同時並行処理できるメリットがでてきます。そのため、bodyタグにいれることはありません。

asyncとdeferのDOMContentLoadedの挙動の違い

asyncの場合、HTMLをパースとscriptのフェッチを同時並行して処理したあと、scriptが実行される間はHTMLのパースが止まります。
DOMContentLoadedを使用すると、asyncのscript以外の実行をまさせることができます。言い換えると、DOMContentLoadedはasyncのscriptは待ちません。
DOMContentLoadedはHTMLのパースが終了した段階で即座に実行されるため、asyncの場合はscriptのフェッチが終わっていない状態でもDOMContentLoadedが発生します。
deferの場合、HTMLのパースとscriptのフェッチの両方が終了した時点で、DOMContentLoadedが発生します。
DOMContentLoadedについては、次の記事を参考にしてみてください。

asyncとdeferの実行の順番の違い

asyncは実行の順番はどうなるかはわかりません。deferは順番通りに実行されます。
そのため、大抵の場合は、deferをheadタグにいれることがベストです。
たとえば外部のライブラリを使うときなどは、asyncだと自分のコードよりも先に実行されることがあるため、deferを使うのが適切です。

順番の無関係な、たとえばGoogle Analyticsなどであれば、asyncでいいでしょう。
そして、古いブラウザはasyncとdeferに対応していない場合があります。
その時は、bodyタグの閉じタグ直前にscriptタグを置きましょう。

asyncとdeferをHTMLへの挿入方法

asyncとdeferをHTMLへの挿入方法はとても簡単です。
通常のscriptにdeferかasyncを記述して、headタグに移すだけです。

<head>
<script defer src="script.js"></script>
<script async src="script.js"></script>
</head>

DOMイベントのライフサイクル

ライフサイクルとは、ユーザーがページアクセスされてからそのページを離れるまでのことです。
DOM content loaded HTMLが完全にパースされたらスタイルシート、画像や外部ソースを待たずに発生させることができます。HTMLをダウンロードしてDOMツリーにいれます。
つまり、画像や外部ソースは後回しにして、HTMLとJavaScriptを先にロードします。
ただし、全体をこれで囲ったりする必要はありません。scriptタグをbodyタグの閉じタグの直前に書いていればOKです。



document.addEventListener('DOMContentLoaded', function (e) {
  console.log('HTML paesed and FOM');
})

モジュールの場合

モジュールの場合、type="module"とした時点で自動的にdeferが付与されるため、書く必要はありません。
モジュールに関しては、次の記事を参考にしてみてください。