scriptタグのasync/deferの使い分け

htmlの<script>タグってどこに書けばいいんだろう?と思って、調べた結果メモ。

 

以下の図がわかりやすい。


引用:https://html.spec.whatwg.org/multipage/scripting.html#attr-script-async

1.headタグの中に書く

以下のように書く方法。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="main.js"></script>
    <title>Document</title>
  </head>
  <body></body>
</html>

この方法だと、ブラウザは以下のように動作する。

  1. HTMLファイルを1行目からパースして行くでー!
  2. <script src="main.js"></script>が出てきたから、HTMLファイルのパースは一時中断して、main.jsをダウンロードしてくるでー!
  3. ダウンロードしてきたmain.jsを実行するでー!
  4. HTMLファイルのパースを再開するでー!

 

この方法だと、ページの主要コンテンツであるbodyタグの中身が描写される前に、javascriptファイルをダウンロード&実行される。

なので、ページが表示されるまでが遅くなる。ユーザーを待たせてしまう。

2.bodyタグの最後に書く

以下のように書く方法。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="main.js"></script>
  </body>
</html>

 

この方法だと、ブラウザは以下のように動作する。(1.と同じ)

  1. HTMLファイルを1行目からパースして行くでー!
  2. <script src="main.js"></script>が出てきたから、HTMLファイルのパースは一時中断して、main.jsをダウンロードしてくるでー!
  3. ダウンロードしてきたmain.jsを実行するでー!
  4. HTMLファイルのパースを再開するでー!

 

1.と違うのは、この方法だとページの主要コンテンツであるbodyタグの中身が描写したあとに、javascriptファイルをダウンロード&実行される。

つまり、ページが表示し終わってからjavascriptが実行されるので、ページの表示速度が遅くなることはない。

 

後述する

  • defer属性
  • async属性

がなかった時代はこの方法が主流だったらしい。

3.async付きでheadタグの中に書く

以下のように書く方法。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="main.js" async></script>
    <title>Document</title>
  </head>
  <body></body>
</html>

この方法だと、ブラウザは以下のように動作する。

  1. HTMLファイルを1行目からパースして行くでー!
  2. <script src="main.js"></script>が出てきたから、HTMLファイルのパースしつつ、並行してmain.jsをダウンロードしてくるでー!
  3. main.jsのダウンロードが終わったから、HTMLファイルのパースは中断してmain.jsを実行するでー!
  4. HTMLファイルのパースを再開するでー!

 

注意点として、asyncをつけるとダウンロードが終わった時点ですぐに実行されてしまう。

例えば、以下のように2つのjsファイルをasyncで読み込むと

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="library.js" async></script>
    <script src="main.js" async></script>
    <title>Document</title>
  </head>
  <body></body>
</html>

本来はlibrary.jsmain.jsという順番で実行されてほしいのに

  • library.jsのファイルサイズが大きかったり
  • library.jsを配信しているサーバーが遅かったり

すると、main.jsのダウンロードが先に完了してしまって、main.jslibrary.jsという順番で実行されてしまったりする。

 

asyncを使うべきなのは、以下のようなものらしい。

  • 広告(Google Adsenseなど)
  • アクセス解析(Google Analyticsなど)

要するに「それ単体で独立していて読み込み順などを意識しなくていいもの」はasyncを使う。

ただ、広告ってページの表示が終わってからワンテンポ遅れて表示されたほうが目が行きやすい(=クリックされやすい)と思うので、あえてasync付けないほうが良いのでは・・・?という気もする。

4.defer付きでheadタグの中に書く

以下のように書く方法。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="main.js" defer></script>
    <title>Document</title>
  </head>
  <body></body>
</html>

 

この方法だと、ブラウザは以下のように動作する。

  1. HTMLファイルを1行目からパースして行くでー!
  2. <script src="main.js"></script>が出てきたから、HTMLファイルのパースしつつ、並行してmain.jsをダウンロードしてくるでー!
  3. main.jsのダウンロードが終わったけど、HTMLファイルのパースが終わるまでmain.jsを実行するのは待つでー!
  4. HTMLファイルのパースが終わったから、main.jsを実行するでー!

 

deferだと順番が保証される。

例えば、以下のように書いた場合、

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="library.js" defer></script>
    <script src="main.js" defer></script>
    <title>Document</title>
  </head>
  <body></body>
</html>

main.jsが先にダウンロードが終わったとしても、library.jsが実行されるまでmain.jsは実行されない。

 

実行のタイミングは、DOMContentLoaded イベントの直前。

まとめ

なし async defer
読み込み 同期 非同期 非同期
実行の順番 コード順 ダウンロードが完了した順 コード順
実行のタイミング コード順 ダウンロードが完了した直後 DOMContentLoadedイベントの直前。
ブラウザの対応 どのブラウザでもOK 大体どのブラウザでもOK IE以外はすべて対応。
IEだけはver10以降限定。

使い分け

自分は以下のように理解しました。

・基本的にdeferを使えばいい。

・広告やアクセス解析などの「それ単体で成り立つよね」みたいなものには、asyncを使うといいかもだけど、別にasyncを使うメリットはないのでdeferでもいい。

・ページ表示前のローディングアニメーションのような「絶対にこのタイミングで実行させておきたい!」のような場合は、何も付けずに書く。

・IEなどの旧ブラウザも考慮しなければいけない場合は、何も付けずに以下のようにする。
→ページ表示前に実行させたい場合:headタグの中に書く。
→ページ表示後に実行させたい場合:
bodyタグの中の最後に書く。もしくはheadタグの中に書き、addEventListenerDOMContentLoadedが発火したタイミングで実行するようにする(以下のようにする)

document.addEventListener('DOMContentLoaded', function () {
    //ここに書く
});

 

おわり

コメント

タイトルとURLをコピーしました