JavaScriptのasyncとdeferの違い|読み込みを最適化して表示速度を改善

コードが書かれたモニター

外部のJavaScriptファイルを読み込むとき、私も昔は何も考えずにこう書いていました。

<script src="script.js"></script>

この書き方でもJavaScriptは動きます。ただ、置く場所やファイルの中身によっては、ページの表示がもたついたり、HTML要素がまだ生成されていないタイミングでスクリプトが走ってエラーになったりします。私も一度、これで「なぜかスライダーが動かない」と半日悩んだことがありました。

そこで出てくるのが async と defer です。どちらもJavaScriptの読み込みを効率化する属性ですが、動き方はけっこう違います。

この記事では、asyncとdeferの違い、使い分け、WordPressサイトでの注意点、PageSpeed Insights改善との関係を、MDNやHTML仕様の記述にあたりながら整理します。

JavaScriptの読み込みが表示速度に影響する理由

HTMLは上から順に読み込まれていきます。その途中で普通のscriptタグに出くわすと、ブラウザはHTMLの解析をいったん止め、JavaScriptファイルを取りに行って実行します。

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

つまり、そのJavaScriptの取得と実行が終わるまで、HTMLの解析が足踏みするわけです。すると、こんな問題が起きやすくなります。

  • ページの表示開始が遅くなる
  • ファーストビューの描画が遅れる
  • DOM要素がまだ無い状態でJavaScriptが実行される
  • PageSpeed Insightsで改善項目として指摘される

とくに、ファーストビューに関係ないJavaScriptを早い段階で読み込んでいると、表示速度への悪影響が出やすいです。

asyncとは

asyncは、JavaScriptファイルをHTMLの解析と並行してダウンロードし、ダウンロードが終わったところですぐ実行する属性です。MDNの表現を借りると「解析と並行して取得され、利用可能になり次第すぐに評価される」動きになります。

<script src="analytics.js" async></script>

特徴はこうです。

  • HTML解析と並行してダウンロードする
  • ダウンロードが終わり次第、すぐ実行される
  • 実行順は保証されない
  • DOMContentLoadedを待たない
  • DOM操作をするスクリプトには注意が必要

ひとつ覚えておきたいのは、ダウンロード自体は並行でも、実行の瞬間はHTML解析が止まるという点です。読み込みが早く終われば、解析の途中でも割り込んで実行されます。だからこそ実行順が読めず、「読み終わったらすぐ動かしていい、他に依存しないスクリプト」に向いた属性だと言えます。

asyncに向いているスクリプト

asyncが合うのは、他のスクリプトやDOM要素への依存が少ないものです。

  • アクセス解析タグ
  • 広告タグ
  • 外部サービスの計測タグ
  • 単独で動く第三者スクリプト

Googleアナリティクスや広告関連のタグでasyncが使われるのは、こうした理由からです。逆に、他の処理に依存するJavaScriptや、HTML要素を操作するJavaScriptには向きません。

deferとは

deferも、JavaScriptファイルをHTMLの解析と並行してダウンロードします。違うのは実行のタイミングで、HTMLの解析が終わってから、DOMContentLoadedが発火する直前に実行されます。

<script src="main.js" defer></script>

特徴はこうです。

  • HTML解析と並行してダウンロードする
  • HTML解析が終わってから実行される
  • 複数のdefer付きscriptは記述順に実行される
  • DOM操作をするスクリプトと相性がよい
  • DOMContentLoadedの直前に実行される

deferは、普通のサイト制作ではとにかく使いやすい属性です。HTMLの読み込みを邪魔せず、DOMが揃った状態で実行されるので、メニューやアコーディオン、スライダー、フォーム制御などとも相性がいい。しかも記述した順に動いてくれるので、依存関係があるスクリプトでも安心して並べられます。

asyncとdeferの違い

ふたつの違いを表にすると、次のようになります。

項目 async defer
ダウンロード HTML解析と並行 HTML解析と並行
実行タイミング 読み込み完了後すぐ(解析中でも割り込む) HTML解析完了後、DOMContentLoadedの直前
実行順 保証されない 記述順に実行
DOM操作 注意が必要 比較的向いている
向いている用途 解析・広告など独立した処理 サイト本体のJavaScript

迷ったら、サイト本体のJavaScriptにはdeferを選んでおくほうが無難です。

通常のscript、async、deferの違いをコードで比較

通常のscript

<script src="main.js"></script>

HTML解析の途中で読み込みと実行が行われます。置く場所によっては、ページ表示やDOM操作に影響します。

async

<script src="analytics.js" async></script>

HTML解析と並行して読み込み、読み込みが終わったらすぐ実行します。実行順が大事なスクリプトには不向きです。

defer

<script src="main.js" defer></script>

HTML解析と並行して読み込み、解析が終わってから実行します。複数あっても記述順に動きます。

body末尾に置く従来のやり方との違い

asyncやdeferが広まる前は、scriptタグを</body>の直前に置くのが定番でした。

  ...
  <script src="main.js"></script>
</body>

これでもHTMLの解析を止めずにすみ、DOMもほぼ揃った状態で実行できます。今でも通用するやり方です。

ただ、body末尾に置いた場合、ダウンロードが始まるのはブラウザがそのタグまで解析を進めてからです。一方deferなら、head内に書いても解析と並行してダウンロードを先に始められて、実行だけ後回しにできます。読み込みの早さという点では、deferのほうが一歩有利という理解でいいと思います。

インラインスクリプトには効かない点に注意

見落としがちなのが、asyncもdeferも、src属性のない(=直接書いた)インラインスクリプトには効かないという点です。MDNでも「src属性が無い場合は指定してはならず、効果を持たない」とはっきり書かれています。

<script defer>
  // ここに直接書いたコードにdeferは効かない
</script>

こう書いても、このコードは書いた位置でそのまま実行されます。遅らせたいなら外部ファイルに切り出すか、DOMContentLoadedのイベントで囲むなどの対応が必要です。

type=”module”は最初からdefer相当

もうひとつ知っておくと便利なのが、type="module"のスクリプトです。

<script type="module" src="app.js"></script>

モジュールスクリプトは既定でdefer相当の動きになります。MDNには「defer属性はモジュールスクリプトには効果がない。モジュールは既定で遅延される」とあります。つまりdeferをわざわざ付ける必要はなく、HTML解析が終わってから記述順に実行されます。asyncを付ければ、モジュールでも読み込み次第すぐ実行という挙動に変わります。

asyncを使うときの注意点

asyncは便利ですが、使いどころを外すと不具合の元になります。とくに気をつけたいのがこのあたりです。

  • jQuery本体より先にjQuery依存スクリプトが実行される
  • DOM要素がまだ無い状態で処理が走る
  • 複数のJavaScriptの実行順が前後する
  • スライダーやメニューが動かなくなる

たとえば、次のような指定は危険です。

<script src="jquery.js" async></script>
<script src="custom.js" async></script>

この並びだと、custom.jsがjquery.jsより先に実行される可能性があります。custom.jsがjQueryに依存していれば、当然エラーになります。私が冒頭で半日溶かしたのも、まさにこのパターンでした。

deferを使うときの注意点

deferは扱いやすい反面、何にでも付ければいいわけではありません。次の点は頭に入れておきたいところです。

  • インラインスクリプトには基本的に使わない(そもそも効かない)
  • 古い外部スクリプトは動作の前提が違う場合がある
  • 広告タグや解析タグは提供元の推奨コードを確認する
  • WordPressプラグインが出力するスクリプトを無理に変更しない

とくにWordPressでは、テーマやプラグインが大量のJavaScriptを出力しています。そこに一括でdeferをかけると、一部の機能が動かなくなることがあります。

WordPressでasyncやdeferを使う場合

WordPressでは、wp_enqueue_script()を使ってJavaScriptを読み込むのが基本です。テーマ内に直接scriptタグを書くより、WordPressの仕組みに乗せたほうが管理しやすくなります。

たとえばテーマのJavaScriptを読み込むなら、こんな感じです。

function my_theme_scripts() {
  wp_enqueue_script(
    'theme-main',
    get_template_directory_uri() . '/js/main.js',
    array(),
    '1.0.0',
    true
  );
}
add_action('wp_enqueue_scripts', 'my_theme_scripts');

最後のtrueは、フッター側で読み込む指定です。

最近のWordPressには、スクリプト読み込み時に属性を付ける仕組みも用意されています。テーマやWordPressのバージョンに合わせて、deferやasyncを適切に付けていくといいでしょう。ただ、すべてのスクリプトへ機械的に付けるのはおすすめしません。

PageSpeed Insights対策としての考え方

PageSpeed Insightsでは、JavaScriptの読み込みや実行が表示速度の足を引っ張っていることがあります。たとえば、こんなチェックに関係してきます。

  • レンダリングを妨げるリソースの除外
  • 使用していないJavaScriptの削減
  • メインスレッド作業の最小化
  • LCPの改善

とはいえ、asyncやdeferを付ければ必ずスコアが上がる、というものでもありません。大事なのは、こう整理して考えることです。

  • ファーストビューに不要なJavaScriptは遅らせる
  • 依存関係のあるJavaScriptは実行順を守る
  • 不要なプラグインのスクリプトはそもそも読み込まない
  • 広告・解析タグは提供元の推奨方法を確認する
  • 変更したら必ず実機で動作確認する

表示速度を上げるには、属性を足すだけでなく、JavaScriptそのものを減らすことも欠かせません。

asyncとdeferの使い分けまとめ

基本的な使い分けはこの通りです。

スクリプトの種類 おすすめ
アクセス解析 async
広告タグ 提供元の推奨に従う
DOM操作を行う自作JS defer
jQuery依存のJS deferまたはフッター読み込み
他のJSに依存しない外部タグ async

迷ったら、サイト本体のJavaScriptにはdefer、独立した解析タグにはasync。この線引きで考えると分かりやすいはずです。

参考:MDN:script 要素HTML Standard(WHATWG):script要素の属性

まとめ

asyncとdeferは、どちらもJavaScriptの読み込みを効率化する属性ですが、動き方は違います。

  • asyncは読み込み完了後すぐ実行される
  • deferはHTML解析完了後、DOMContentLoadedの直前に実行される
  • asyncは実行順が保証されない
  • deferは記述順に実行される
  • どちらもインラインスクリプトには効かない
  • type=”module”は既定でdefer相当
  • DOM操作をする自作JavaScriptにはdeferが使いやすい

JavaScriptの読み込み方は、ページの表示速度と動作の安定性に直結します。とくにWordPressでは、テーマ・プラグイン・広告・解析タグと、いろいろなJavaScriptが集まりがちです。何でも一括で遅延させればいいわけではないので、スクリプトの役割ごとにasyncとdeferを使い分けてみてください。