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のスコアも上がります。

タイミングによってはイベントが発生しない
一見、async属性を追加するだけで高速化ができ、PageSpeed Insightsのスコアが上がるのであれば手当たり次第にasync属性を追加したくなると思いますが、ちょっと待ってください。
非同期になるということはそのJavaScriptがいつ実行されるかまったく保証されません。レンダリングの途中で実行されるかもしれないし、レンダリングが終わったあとに実行されるかもしれないのです。
もし、レンダリングが終わったあとに実行された場合、比較的よく使用されるイベントである「DOMContentLoaded
」イベントが発生しないのです。
現象の再現
次のサンプルページで現象を再現できます。ただし、回線の速さ・PCの処理の速さによっては正常に読み込めてしまう可能性があります。
対処方法
上記ページを見て察し頂けたかと思いますが、対処方法はいくつかあります。
1. 「load」関数に代替えする
「DOMContentLoaded
」イベントを使用せずに「load
」関数を使用する方法です。「load
」関数はすべての準備(JavaScriptや画像の読み込みなど)が終わってから実行されます。しかし、準備を待つために場合によっては実行タイミングがとても遅いため注意が必要です。
2. すでにレンダリングされている場合は即時に実行する
レンダリング完了後は、document.readyState
がcomplete
またはinteractive
となっているため、次のようなif文で、loading
以外であれば即時実行するようにプログラムを変更します。
if (document.readyState !== 'loading') {
eventHandler();
} else {
document.addEventListener('DOMContentLoaded', eventHandler, false);
}
3. jQueryを使用する
jQueryの$(function(){}
では、async
属性であっても、レンダリング中であれば「DOMContentLoaded
」のタイミングで実行、レンダリングが終わっていれば即時実行されます。内部的には2項の方法で対策しているようです。
※当然ながらjQueryライブラリがイベントプログラムよりも先に読まれないと正常に動作しません(jQueryライブラリはasyncで読み込めません)。
コメント