JavaScript
Web
performance

<script> タグに async / defer を付けた場合のタイミング

HTML5 においては、<script> タグに、deferasync 属性を付与することで、これらの読込が HTML パースを妨害しないようにすることができます。これにより、サイトのメインコンテンツの読込がある程度高速化されます。

async にせよ defer にせよ、それほど詳細に動作を解説した記事が見つかりませんでしたので、本記事では、これらの動作について解説します。

イベントについて

まずは、asyncdefer の挙動を理解するために重要な、2つのイベントについて理解しましょう。
両者とも、MDN に十分な情報があるため、本記事ではそれほど詳細には解説しません。

DOMContentLoaded

HTML のパースが完了した直後に発火します。

MDN

load

HTML のパースが完了し、CSS や画像などのダウンロードと表示、JavaScript ファイルのダウンロードと実行など、全てのリソースの処理が完了した (このタイミングを Document Complete とも呼びます) 直後に発火します。

MDN

読込の方法

どのようなタイミングで関して、WHATWG の規格書には、非常にわかりやすい図が添えられています。

これをベースに、イベント等を追加し、動作の図を作成しました。
script-lazy-async-defer.png

1. 同期的読込 (デフォルト)

<script> タグに deferasync も付けないと、HTML パース中に <script> タグにたどり着き次第、パースを一時停止し、JS ファイルのダウンロードと実行を行います。
JS ファイルの実行が完了するまで、HTML のパースは再開されません。

実装コード:

<script src="https://third-party.com/script.js">

2. async

<script> タグに async 属性を追加することで、非同期に JS ファイルをダウンロード・実行します。

ダウンロードによって、HTML のパースを止めることはなく、並行してダウンロードが進みます。
ダウンロード完了後即 JS ファイルは実行され、実行中は HTML パースが一時停止します。

また、複数の <script> タグが async 付きで記述されている場合、それらの実行順序は <script> タグを書いた順には実行されず、ランダムになります。

上の図では、ダウンロード・実行共にパース前に完了していますが、ダウンロード完了 (≒ 実行開始) や実行完了が、DOMContentLoaded イベント発火後になる場合もあります。
load イベントよりは前に実行されます。JavaScript ファイルの実行完了が、load イベント発火の条件でもあるからです。

実装コード:

<script src="https://third-party.com/script.js" async>

3. defer

<script> タグに defer 属性を追加することで、HTML パース完了後、DOMContentLoaded イベントの直前に (※WHATWG 仕様) JS ファイルを実行します。
実行はパース完了後ですが、ダウンロードは async 同様 <script> タグがパースされ次第即座に非同期に実行されます。

また、async とは異なり、複数の <script> タグが defer 付きで記述されている場合、<script> タグを書いた順に実行されるのも特徴です。

例えば、

<script src="/a.js" defer></script>
<script src="/b.js" defer></script>
<script src="/c.js" defer></script>

というような記述の場合、a.js の処理が完了してから b.js が実行され、b.js の処理が実行されてから c.js の処理が実行されます。

実装コード:

<script src="https://third-party.com/script.js" defer>