JavaScript を遅延・スケジュールする14の方法

JavaScript の遅延とスケジュールの方法を学ぶ

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2024-11-27

なぜ JavaScript を遅延またはスケジュールするのか?

JavaScript はいくつかの方法でウェブサイトを遅くする可能性があります(そして実際にそうなります)。これは Core Web Vitals にあらゆる種類の悪影響を及ぼす可能性があります。JavaScript はネットワークリソースを奪い合い、CPUリソースを奪い合い(メインスレッドをブロック)、さらにはウェブページのパースをブロックすることさえあります。スクリプトの遅延とスケジュールにより、Core Web Vitals を大幅に改善できます。

JavaScript が Core Web Vitals に及ぼす悪影響を最小限に抑えるには、スクリプトがダウンロードのキューに入るタイミングと スケジュール CPUタイムを使用しメインスレッドをブロックできるタイミングを指定することが通常は良い方法です。

JavaScript のタイミングは Core Web Vitals にどう影響するのか?

JavaScript のタイミングは Core Web Vitals にどう影響するのでしょうか?この実例を見てみましょう。最初のページは「レンダーブロッキング」JavaScript で読み込まれています。 ペイント指標とインタラクティブになるまでの時間はかなり悪い結果です。2番目の例はまったく同じページですが、JavaScript が遅延されています。LCP 画像はまだ大きな影響を受けていることがわかります。3番目の例は、ページの「load イベント」後に同じスクリプトが実行され、関数呼び出しが小さなピースに分割されています。この最後の例は、余裕を持って Core Web Vitals に合格しています。

js render blocking lhnull
js deferred lhnull
js after load lhnull


デフォルトでは、ページの head にある外部 JavaScript はレンダーツリーの生成をブロックします。より具体的には、ブラウザがドキュメント内のスクリプトに遭遇すると、 DOM の構築を一時停止し、JavaScript ランタイムに制御を渡し、DOM の構築を続行する前にスクリプトを実行させなければなりません。これはペイント指標(Largest Contentful Paint と First Contentful Paint)に影響します。

遅延または非同期の JavaScript でもペイント指標に影響を与える可能性があります。 特に Largest Contentful Paint に影響します。DOM が生成された後に実行され、メインスレッドをブロックするためです(一般的な LCP 要素であるがまだダウンロードされていない場合があります)。

外部 JavaScript ファイルもネットワークリソースを奪い合います。 外部 JavaScript ファイルは通常、画像よりも先にダウンロードされます。スクリプトを多くダウンロードしすぎると、画像のダウンロードが遅れることになります。

最後に重要なこととして、JavaScript はユーザーインタラクションをブロックまたは遅延させる可能性があります。スクリプトがCPUリソースを使用している(メインスレッドをブロックしている)とき、ブラウザはそのスクリプトが完了するまで入力(クリック、スクロールなど)に応答しません。

JavaScript のスケジュールまたは遅延はどのように Core Web Vitals を改善するのか?

JavaScript のスケジュールまたは遅延は、それ自体で Core Web Vitals を修正するわけではありません。適切な状況に適切なツールを使用することがすべてです。原則として、スクリプトの遅延をできるだけ少なくしつつ、ダウンロードのキューに入れ、適切なタイミングで実行するようにすべきです。

適切な遅延方法の選び方

すべてのスクリプトが同じではなく、各スクリプトにはそれぞれの機能があります。レンダリングプロセスの早い段階で必要なスクリプトもあれば、そうでないものもあります。

js defer vs async vs blockingnull

私は JavaScript を重要度に基づいて4つのグループに分類するのが好きです。

1.レンダークリティカル。これはウェブページの外観を変更するスクリプトです。読み込まれなければ、ページは完全に表示されません。これらのスクリプトはできる限り避けるべきです。何らかの理由で避けられない場合は、遅延すべきではありません。例えば、トップスライダーやA/Bテストスクリプトなど。
2. クリティカル。これらのスクリプトはウェブページの外観を(大きくは)変更しませんが、なければページがうまく機能しません。これらのスクリプトは遅延または非同期にすべきです。例えば、メニュースクリプトなど。
3. 重要。あなたや訪問者にとって価値があるため読み込みたいスクリプトです。私はこれらのスクリプトを load イベントが発火した後に読み込む傾向があります。例えば、アナリティクスや「トップに戻る」ボタンなど。
4. あると便利。どうしても必要なら、なくても生きていけるスクリプトです。私はこれらのスクリプトを最も低い優先度で読み込み、ブラウザがアイドル状態のときにのみ実行します。例えば、チャットウィジェットや Facebook ボタンなど。

方法1:defer 属性を使用する

defer 属性を持つスクリプトは並行してダウンロードされ、遅延 JavaScript キューに追加されます。ブラウザが DOMContentLoaded イベントを発火する直前に、そのキュー内のすべてのスクリプトがドキュメントに表示される順序で実行されます。

<script  src='javascript.js'></script>

「defer テクニック」は通常、多くの問題を解決します。特にペイント指標に効果的です。残念ながら保証はありません。スクリプトの品質に依存します。遅延されたスクリプトは、すべてのスクリプトがロードされ HTML がパースされた後(DOMContentLoaded)に実行されます。LCP 要素(通常は大きな画像)はその時点ではまだロードされていない可能性があり、遅延されたスクリプトが LCP の遅延を引き起こす可能性があります。

使用するタイミング

できるだけ早く必要なクリティカルスクリプトに遅延スクリプトを使用してください。

利点

  1. 遅延スクリプトは並行してダウンロードされます
  2. 実行時に DOM が利用可能です

欠点

  1. 遅延スクリプトは LCP 指標を遅延させる可能性があります 
  2. 遅延スクリプトは実行されるとメインスレッドをブロックします
  3. インラインスクリプトや非同期スクリプトが依存している場合、スクリプトを遅延させるのは安全でない場合があります

方法2:async 属性を使用する

async 属性を持つスクリプトは並行してダウンロードされ、ダウンロードが完了するとすぐに実行されます。

<script  src='javascript.js'></script>

非同期スクリプトはページスピードの問題をほとんど解決しません。並行してダウンロードされるのは素晴らしいことですが、それだけです。ダウンロードが完了すると、実行時にメインスレッドをブロックします。

使用するタイミング

できるだけ早く必要で、自己完結型(他のスクリプトに依存しない)のクリティカルスクリプトに非同期スクリプトを使用してください。

利点

  1. 非同期スクリプトは並行してダウンロードされます。
  2. 非同期スクリプトはできるだけ早く実行されます。

欠点

  1. DOMContentLoaded は非同期の前後どちらでも発生する可能性があります。
  2. スクリプトの実行順序は事前にはわかりません。
  3. 他の非同期スクリプトや遅延スクリプトに依存する非同期スクリプトは使用できません

方法3:モジュールを使用する

モジュラースクリプトは、async 属性がない限り、デフォルトで遅延されます。async 属性がある場合は、非同期スクリプトとして扱われます。

<script  src='javascript.js'></script>

モジュールは JavaScript の新しい考え方であり、いくつかの設計上の欠陥を修正します。それ以外では、スクリプトモジュールを使用してもウェブサイトは高速化しません。 

使用するタイミング

アプリケーションがモジュラーな方法で構築されている場合、JavaScript モジュールを使用することは理にかなっています。

利点

  1. モジュールはデフォルトで遅延されます
  2. モジュールはメンテナンスが容易で、モジュラーなウェブデザインとうまく機能します
  3. モジュールは、特定のタイミングで必要なモジュールのみをインポートする動的インポートによる簡単なコード分割を可能にします。

欠点

  1. モジュール自体は Core Web Vitals を改善しません
  2. ジャストインタイムまたはオンザフライでモジュールをインポートすると、遅くなり FID や INP が悪化する可能性があります 

方法4:スクリプトをページの下部近くに配置する

フッタースクリプトは、より遅いタイミングでダウンロードのキューに入れられます。これにより、スクリプトタグよりもドキュメント内の上にある他のリソースが優先されます。

<html>
   <head></head>
   <body>
      [your page contents here]
      <script defer src='javascript.js'></script>
   </body>
</html>

スクリプトをページの下部に配置するのは興味深いテクニックです。これにより、画像などの他のリソースがスクリプトよりも先にスケジュールされます。JavaScript ファイルのダウンロードが完了してメインスレッドがスクリプトの実行によりブロックされる前に、それらがブラウザで利用可能になり画面に描画される可能性が高くなります。ただし...保証はありません。

使用するタイミング

スクリプトがすでにかなりうまく機能しているが、画像やウェブフォントなどの他のリソースをわずかに優先したい場合。

利点

  1. スクリプトをページの下部に配置するのに多くの知識は必要ありません。
  2. 正しく行えば、ページが壊れるリスクはありません

欠点

  1. クリティカルスクリプトのダウンロードと実行が遅くなる可能性があります
  2. 根本的な JavaScript の問題は修正されません

方法5:スクリプトを注入する

注入されたスクリプトは非同期スクリプトとして扱われます。並行してダウンロードされ、ダウンロード直後に実行されます。

<script>
    const loadScript = (scriptSource) => {
        const script = document.createElement('script');
        script.src = scriptSource;
        document.head.appendChild(script);
    }
    
    // call the loadscript function that injects 'javascript.js'
    loadScript('javascript.js');
</script>

Core Web Vitals の観点からは、このテクニックは <script async> を使用するのとまったく同じです。

使用するタイミング

この方法は、できるだけ早くトリガーするサードパーティスクリプトでよく使用されます。関数呼び出しにより、コードのカプセル化と圧縮が容易になります。

利点

  1. 非同期スクリプトを注入する、自己完結型のコード。

欠点

  1. DOMContentLoaded はスクリプトのロードの前後どちらでも発生する可能性があります。
  2. スクリプトの実行順序は事前にはわかりません。
  3. 他の非同期スクリプトや遅延スクリプトに依存するスクリプトには使用できません

方法6:より遅いタイミングでスクリプトを注入する

あると便利なスクリプトは、私の意見では遅延で読み込むべきではありません。最も適切なタイミングで注入すべきです。以下の例では、ブラウザが「load」イベントを送信した後にスクリプトが実行されます。

<script>
window.addEventListener('load', function () {
  // see method 5 for the loadscript function
  loadScript('javascript.js');
});
</script>

これは Largest Contentful Paint を確実に改善する最初のテクニックです。ブラウザが「load イベント」を発火するとき、画像を含むすべての重要なリソースがダウンロード済みです。ただし、load イベントが呼び出されるまでに長い時間がかかる可能性があるため、あらゆる種類の問題が発生する可能性があります。

使用するタイミング

ペイント指標に影響を与える理由のない、あると便利なスクリプトに。

利点

  1. ページとそのリソースがロードされた後にスクリプトを注入するため、クリティカルリソースと競合しません

欠点

  1. ページが Core Web Vitals の観点で適切に設計されていない場合、ページが「load」イベントを送信するまでに長い時間がかかる可能性があります
  2. クリティカルスクリプト(遅延読み込み、メニューなど)にこれを適用しないよう注意する必要があります

方法7:スクリプトの type を変更する(そして元に戻す)

ページ上のスクリプトタグが 1. type 属性を持ち、2. その type 属性が "text/javascript" でない場合、そのスクリプトはブラウザによってダウンロードおよび実行されません。多くの JavaScript ローダー(CloudFlare の RocketLoader など)はこの原理に依存しています。アイデアは非常にシンプルでエレガントです。

まず、すべてのスクリプトが次のように書き換えられます:

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

そして、読み込みプロセスのある時点で、これらのスクリプトは「通常の JavaScript」に変換し直されます。

使用するタイミング

これは私が推奨する方法ではありません。JavaScript の影響を修正するには、すべてのスクリプトをキューの少し後ろに移動する以上のことが必要です。一方で、ページで実行されているスクリプトをほとんど制御できない場合や、JavaScript の知識が不十分な場合は、これが最善の選択肢かもしれません。

利点

  1. 簡単です。Rocket Loader や別のプラグインを有効にするだけで、すべてのスクリプトがやや遅いタイミングで実行されるようになります。
  2. JavaScript ベースの遅延読み込みを使用していなければ、ペイント指標が改善される可能性が高いです。
  3. インラインスクリプトと外部スクリプトの両方に機能します。

欠点

  1. スクリプトの実行タイミングを細かく制御できません
  2. 書き方の悪いスクリプトが壊れる可能性があります
  3. JavaScript を修正するために JavaScript を使用します
  4. 長時間実行されるスクリプトの問題は解決しません

方法8:Intersection Observer を使用する

Intersection Observer を使用すると、要素が表示可能なビューポートにスクロールされたときに関数(この場合は外部 JavaScript を読み込む関数)を実行できます。

<script>
const handleIntersection = (entries, observer) => {
    if (entries?.[0].isIntersecting) {
        // load your script or execute another 
           function like trigger a lazy loaded element
        loadScript('javascript.js');

        // remove the observer
        observer.unobserve(entries?.[0].target);
    }
};
const Observer = new window.IntersectionObserver()
Observer.observe(document.querySelector('footer'));
</script>

これは JavaScript を遅延する最も効果的な方法です。必要なスクリプトだけを、必要になる直前に読み込みます。残念ながら、実際にはこれほどきれいではなく、ビューに入る要素に紐づけられるスクリプトは多くありません。

使用するタイミング

このテクニックをできるだけ多く使用してください!スクリプトが画面外のコンポーネント(マップ、スライダー、フォームなど)とのみやり取りする場合、これがスクリプトを注入する最良の方法です。

利点

  1. Core Web Vitals の LCP と FCP に干渉しません
  2. 使用されないスクリプトを注入することはありません。これにより FID と INP が改善されます

欠点

  1. 表示可能なビューポートにある可能性のあるコンポーネントには使用すべきではありません
  2. 基本的な <script src="..."> よりもメンテナンスが難しいです
  3. レイアウトシフトを引き起こす可能性があります

方法9:readystatechange を使用する

document.readystate は「DOMContentLoaded」と「load」イベントの代替として使用できます。「interactive」readystate は通常、DOM を変更したりイベントハンドラを追加したりする必要があるクリティカルスクリプトを呼び出すのに適した場所です。 
「complete」readystate は、それほどクリティカルでないスクリプトを呼び出すのに適した場所です。

document.addEventListener('readystatechange', (event) => {
  if (event.target.readyState === 'interactive') {
    initLoader();
  } else if (event.target.readyState === 'complete') {
    initApp();
  }
});

方法10:timeout なしの setTimeout を使用する

setTimeout はページスピードコミュニティでは嫌われていますが、大幅に過小評価されている方法です。setTimeout は誤用されることが多いため、悪い評判を得ています。 多くの開発者は setTimeout がスクリプトの実行を設定されたミリ秒数だけ遅延させるためにのみ使用できると信じています。これは事実ですが、setTimeout は実際にはさらに興味深いことを行います。ブラウザのイベントループの最後に新しいタスクを作成します。この動作を利用して、タスクを効果的にスケジュールできます。また、長いタスクを個別の小さなタスクに分割するためにも使用できます。

<script>
   setTimeout(() => {
       // load a script or execute another function
       console.log('- I am called from a 0ms timeOut()')
    }, 0);

   console.log('- I was last in line but executed first')
   /*
      Output:
      - I was last in line but executed first
      - I am called from a 0ms timeOut()
   */
</script>

使用するタイミング

setTimeout はブラウザのイベントループに新しいタスクを作成します。メインスレッドが順次実行される多くの関数呼び出しによってブロックされている場合に使用してください。

利点

  1. 長時間実行されるコードを小さなピースに分割できます。

欠点

  1. setTimeout はかなり粗い方法であり、重要なスクリプトの優先順位付けを提供しません。
  2. 実行されるコードをループの最後に追加します

方法11:timeout 付きの setTimeout を使用する

0ms を超える timeout で setTimeout を呼び出すと、さらに興味深いことになります。

<script>
   setTimeout(() => {
       // load a script or execute another function
       console.log('- I am called from a 10ms timeOut()')
    }, 10);

   setTimeout(() => {
       // load a script or execute another function
       console.log('- I am called from a 0ms timeOut()')
    }, 0);

   console.log('- I was last in line but executed first')
   /*
      Output:
      - I was last in line but executed first
      - I am called from a 0ms timeOut()
      - I am called from a 10ms timeOut()
   */
</script>

使用するタイミング

あるスクリプトを別のスクリプトの後にスケジュールする簡単な方法が必要な場合、小さな timeout で対応できます。

利点

  1. すべてのブラウザでサポートされています

欠点

  1. 高度なスケジューリングは提供しません

方法12:Promise を使用してマイクロタスクを設定する

マイクロタスクの作成も JavaScript をスケジュールする興味深い方法です。マイクロタスクは、現在の実行ループが完了した直後に実行されるようにスケジュールされます。

<script>
   const myPromise = new Promise((resolve, reject) => {
      resolve();
   }).then(
      () => {       
         console.log('- I was scheduled after a promise')
         }
   );   
   console.log('- I was last in line but executed first')

   /*
      Output:
      - I was last in line but executed first
      - I was scheduled after a promise
   */
</script>

使用するタイミング

タスクを別のタスクの直後にスケジュールする必要がある場合。 

利点

  1. マイクロタスクはタスクの実行完了直後にスケジュールされます。
  2. マイクロタスクを使用して、同じイベント内の重要度の低い JavaScript コードを遅延させることができます。 

欠点

  1. メインスレッドを小さな部分に分割しません。ブラウザはユーザー入力に応答する機会がありません。
  2. 自分が何をしているか正確に理解していない限り、Core Web Vitals を改善するためにマイクロタスクを使用する必要はおそらくないでしょう。

方法13:マイクロタスクを使用する

queueMicrotask() を使用しても同じ結果が得られます。Promise に対する queueMicrotask() の利点は、わずかに高速であり、Promise を処理する必要がないことです。

<script>
   queueMicrotask(() => {
      console.log('- I am a microtask')
   })
     
   console.log('- I was last in line but executed first')

   /*
      Output:
      - I was last in line but executed first
      - I am a microtask
   */
</script>


方法14:requestIdleCallback を使用する

window.requestIdleCallback() メソッドは、ブラウザのアイドル期間中に呼び出される関数をキューに入れます。これにより、開発者はアニメーションや入力応答などのレイテンシクリティカルなイベントに影響を与えることなく、メインイベントループでバックグラウンドおよび低優先度の作業を実行できます。関数は通常、先入れ先出し順で呼び出されますが、timeout が指定されたコールバックは、timeout が経過する前に実行するために順序が変更される場合があります。

<script>
requestIdleCallback(() => {
    const script = document.createElement('script');
    script.src = 'javascript.js';
    document.head.appendChild(script);
});
</script>

使用するタイミング

あると便利なスクリプトや、ユーザー入力後の非クリティカルなタスクの処理に使用してください

利点

  1. ユーザーへの影響を最小限にして JavaScript を実行できます
  2. FID と INP が改善される可能性が高いです

欠点

  1. ほとんどのブラウザでサポートされていますが、すべてではありません。setTimeout() に fallback するポリフィルがあります。
  2. コードが実行される保証はありません 

方法15:postTask を使用する

このメソッドにより、ユーザーはオプションでタスクが実行されるまでの最小遅延、タスクの優先度、およびタスクの優先度の変更やタスクの中止に使用できるシグナルを指定できます。タスクのコールバック関数の結果で解決されるか、中止理由またはタスクでスローされたエラーで拒否される Promise を返します。

<script>
scheduler.postTask(() => {
    const script = document.createElement('script');
    script.src = 'javascript.js';
    document.head.appendChild(script);
}, { priority: 'background' });
</script>

使用するタイミング

postTask はスクリプトをスケジュールするための完璧な API です。残念ながら、現時点でのブラウザサポートが不十分なため、使用すべきではありません。

利点

  1. JavaScript 実行スケジューリングの完全な制御!

欠点

  1. 一部の重要なブラウザでサポートされていません。

Lab data is not enough.

I analyze your field data to find the edge cases failing your user experience.

Analyze My Data >>

  • Real User Data
  • Edge Case Detection
  • UX Focused
JavaScript を遅延・スケジュールする14の方法Core Web Vitals JavaScript を遅延・スケジュールする14の方法