async属性で読むと「DOMContentLoaded」イベントが発生しない理由

async でJavaScriptを読むと DOMContentLoaded イベントが発生しません。 async 属性で読み込みつつもイベントを発生させる方法を紹介します。

async属性とは

通常、ブラウザは読み込みの途中で <script> タグがあるとそのスクリプトを実行し終わるまで一時的にレンダリングを中断します。JavaScriptの実行タイミングを保証するためです。同期処理とも呼ばれます。
<script src="javascript.js"></script>
しかし、src属性を持つ <script> タグに async 属性を付与することにより、レンダリングが中断されなくなります。JavaScriptの読込・実行とページのレンダリングが並行して行われるようになり、実行タイミングが保証されなくなります。非同期処理と呼ばれます。
<script src="javascript.js" async></script>
非同期になることにより、レンダリングが中断されずにページ表示の高速化が期待できるため、Google などが推奨しています。PageSpeed Insights のスコアも上がります。
Eliminate render-blocking resources - web.dev
https://web.dev/render-blocking-resources/

タイミングによってはイベントが発生しない

一見、 async 属性を追加するだけで高速化ができ、PageSpeed Insightsのスコアが上がるのであれば手当たり次第に async 属性を追加したくなると思いますが、そううまくはいきません。
非同期になるということはそのJavaScriptがいつ実行されるかまったく保証されません。レンダリングの途中で実行されるかもしれないし、レンダリングが終わったあとに実行されるかもしれないのです。
もし、レンダリングが終わったあとに実行された場合、比較的よく使用されるイベントである DOMContentLoaded イベントが発生しないのです。

現象の再現

次のサンプルページで現象を再現できます。ただし、回線の速さ・PCの処理の速さによっては正常に読み込めてしまう可能性があります。

対処方法

上記ページを見て察し頂けたかと思いますが、対処方法はいくつかあります。

1. load イベントに変更する

DOMContentLoaded イベントを使用せずに load イベントを使用する方法です。 load イベントはすべての準備(JavaScriptや画像の読み込みなど)が終わってから実行されます。しかし、準備を待つために場合によっては実行タイミングがとても遅いため注意が必要です。
Window: load イベント - Web API | MDN
https://developer.mozilla.org/ja/docs/Web/API/Window/load_event
window.addEventListener('load', function() {
  // ...
});

2. すでにレンダリングされている場合は即時に実行する

レンダリング完了後は、 document.readyStatecomplete または interactive となっているため、次のようなif文で、 loading 以外であれば即時実行するようにプログラムを変更します。
if (document.readyState !== 'loading') {
  eventHandler();
} else {
  document.addEventListener('DOMContentLoaded', eventHandler, false);
}
Document.readyState - Web API | MDN
https://developer.mozilla.org/ja/docs/Web/API/Document/readyState

3. jQueryを使用する

jQueryの $(function(){} では、async 属性であっても、レンダリング中であれば DOMContentLoaded のタイミングで実行、レンダリングが終わっていれば即時実行されます。内部的には2項の方法で対策しているようです。
※当然ながらjQueryライブラリがイベントプログラムよりも先に読まれないと正常に動作しません(jQueryライブラリはasyncで読み込めません)。
$(function(){
  $('#jquery').text('発火!🔥');
});

@bicstone

大石貴則 (Ōishi Takanori) と申します。 Webエンジニア / セキュリティスペシャリスト / 機械エンジニア です。 プロダクトに幅広く携わり、相互成長し続けられるエンジニアを目指しています。

GitHubLinkedIn