読み込み中...

JavaScriptにおける遅延実行を完全ガイド!7つの方法でマスター

JavaScript遅延実行のイメージ図 JS
この記事は約18分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

JavaScriptの遅延実行テクニックを習得することで、ウェブページのパフォーマンスとユーザーエクスペリエンス(UX)を大幅に向上させることができます。

本記事では、遅延実行の概念から実践的な応用例まで、幅広く解説していきます。

初心者の方にもわかりやすいよう、段階的に説明を進めていきますので、ぜひ最後までお付き合いください。

●JavaScriptの遅延実行とは

JavaScriptにおける遅延実行とは、特定の処理を即座に実行せず、意図的に実行タイミングを遅らせる技術を指します。

この技術を適切に活用することで、ページの読み込み速度を向上させたり、イベントの発火タイミングを精密にコントロールしたりすることが可能となります。

遅延実行は、ウェブアプリケーションの効率を高める上で非常に重要な役割を果たします。

例えば、ユーザーの操作に応じて大量のデータを処理する必要がある場合、遅延実行を利用することで、処理の負荷を分散させ、アプリケーションの応答性を維持することができるのです。

●遅延実行の方法

JavaScriptには、遅延実行を実現するための様々な方法が存在します。

ここでは、代表的な7つの手法について詳しく解説していきます。

各手法の特徴と使用例を理解することで、適切な状況で最適な方法を選択できるようになります。

○setTimeout

setTimeout関数は、指定した時間(ミリ秒)が経過した後に、特定の関数を実行するための手法です。

この関数は、単発の遅延処理を実装する際に非常に便利です。

次のコードは、2秒後にコンソールに「Hello, World!」というメッセージを表示する例です。

setTimeout(function() {
  console.log('Hello, World!');
}, 2000);

このコードでは、無名関数(アロー関数を使用することも可能です)を最初の引数として渡し、2番目の引数で遅延時間を指定しています。

この例では2000ミリ秒、つまり2秒後に関数が実行されます。

setTimeout関数は、単純な遅延処理だけでなく、複雑な非同期処理のフローコントロールにも活用できます。

例えば、APIリクエストの再試行やユーザー操作後の自動保存機能などに応用可能です。

○setInterval

setInterval関数は、指定した時間間隔で繰り返し処理を実行するための手法です。

定期的に実行する必要がある処理に適しています。

次のコードは、1秒ごとにコンソールに「Hello, World!」と表示する例です。

setInterval(function() {
  console.log('Hello, World!');
}, 1000);

この関数は、繰り返し実行が必要な処理、例えばリアルタイムデータの更新やアニメーション効果の実装などに適しています。

ただし、長時間にわたって実行し続けると、パフォーマンスに悪影響を及ぼす可能性があるため、使用する際は注意が必要です。

必要がなくなった時点で、clearInterval関数を使用してタイマーを停止することを忘れないようにしましょう。

○Promiseとasync/await

Promiseとasync/awaitは、非同期処理の実行順序を制御するための手法です。

これを使用することで、コールバック地獄を避け、より読みやすく保守性の高いコードを書くことができます。

次のコードは、Promiseとasync/awaitを使用して2秒後にメッセージを表示する例です。

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function delayedHello() {
  await delay(2000);
  console.log('Hello, World!');
}

delayedHello();

この方法は、複数の非同期処理を順序立てて実行する必要がある場合に特に有効です。

例えば、APIからデータを取得し、そのデータを処理してから別のAPIにリクエストを送るような一連の処理を、読みやすく管理しやすい形で実装することができます。

○requestAnimationFrame

requestAnimationFrameは、ブラウザの描画タイミングに合わせて関数を実行する手法です。

主にアニメーションの実装に使用されますが、他の用途にも応用可能です。

次のコードは、次の描画タイミングでメッセージを表示する例です。

requestAnimationFrame(function() {
  console.log('Hello, World!');
});

この関数は、ブラウザの描画サイクルに合わせて実行されるため、スムーズなアニメーションを実現できます。

また、非表示のタブやバックグラウンドで実行されている場合は自動的に一時停止するため、リソースの節約にも貢献します。

複雑なアニメーションや、画面の更新に合わせて処理を行う必要がある場合に特に有用です。

○デバウンスとスロットリング

デバウンスとスロットリングは、高頻度で発生するイベント(例:スクロールやリサイズ)の処理を制御するための手法です。

この技術を使用することで、パフォーマンスの低下を防ぎつつ、必要な処理を確実に実行することができます。

デバウンスは、イベント発火後に一定時間が経過したら処理を実行する方法です。

例えば、検索ボックスの入力補完機能などに使用されます。

一方、スロットリングは、一定時間ごとに処理を実行する方法です。

例えば、スクロール位置に応じて要素を表示・非表示にする処理などに適しています。

この技術を実装するには、通常カスタム関数を作成するか、lodashなどのライブラリを使用します。

○イベントリスナー

イベントリスナーを使用した遅延実行は、特定のイベントが発生した時に処理を実行する方法です。

ユーザーの操作に応じて処理を行う場合に適しています。

ここでは、ボタンがクリックされた時にメッセージを表示する例を紹介します。

document.querySelector('button').addEventListener('click', function() {
  console.log('Button clicked!');
});

イベントリスナーは、ユーザーインタラクションに応じた処理を実装する際に非常に重要です。

クリック、キーボード入力、マウスの動きなど、様々なイベントに対して処理を設定することができます。

○Web Worker

Web Workerは、メインスレッドをブロックせずにバックグラウンドで処理を実行するための手法です。

重い計算処理や大量のデータ処理を行う際に有効です。

Web Workerを使用するには、別のJavaScriptファイルを作成し、そこに処理を記述します。

メインスクリプトからはこのWorkerを呼び出して使用します。

// main.js
const worker = new Worker('worker.js');
worker.postMessage('Start processing');

worker.onmessage = function(event) {
  console.log('Received from worker:', event.data);
};

// worker.js
self.onmessage = function(event) {
  console.log('Received in worker:', event.data);
  // 重い処理を行う
  self.postMessage('Processing complete');
};

Web Workerは、ユーザーインターフェースの応答性を維持しながら、複雑な計算や大量のデータ処理を行う必要がある場合に特に有用です。

例えば、画像処理や大規模なデータ分析などに活用できます。

●遅延実行の応用例

ここでは、遅延実行を活用した実践的なサンプルコードを4つ紹介します。

この例を参考に、実際のプロジェクトでの応用方法を考えてみてください。

○サンプルコード1:スクロールイベントの最適化

スクロールイベントは高頻度で発生するため、そのままでは処理が重くなる可能性があります。

デバウンスを使用することで、この問題を解決できます。

let timer;

window.addEventListener('scroll', function() {
  clearTimeout(timer);
  timer = setTimeout(function() {
    console.log('Scrolled');
  }, 200);
});

このコードでは、スクロールイベントが発生するたびにタイマーをリセットし、200ミリ秒間スクロールが止まった時点で処理を実行します。

これにより、スクロール中の不必要な処理を避けつつ、スクロールが止まった時点で必要な処理を確実に実行することができます。

○サンプルコード2:画像の遅延読み込み

画像の遅延読み込み(レイジーロード)は、ページの初期ロード時間を短縮し、不要なデータ転送を減らすために有効な技術です。

次のコードは、画像が表示領域に入った時に読み込む遅延読み込みの例です。

document.addEventListener('DOMContentLoaded', function() {
  const lazyImages = [].slice.call(document.querySelectorAll('img.lazy'));

  function lazyLoad() {
    lazyImages.forEach(function(img) {
      if (img.getBoundingClientRect().top <= window.innerHeight && img.getAttribute('data-src')) {
        img.src = img.getAttribute('data-src');
        img.removeAttribute('data-src');
      }
    });
  }

  window.addEventListener('scroll', lazyLoad);
  window.addEventListener('resize', lazyLoad);
  lazyLoad();
});

このコードでは、img.lazyクラスを持つ画像要素を対象に、画面内に入ったタイミングでdata-src属性の値をsrc属性に設定しています。

これにより、初期ロード時には画像を読み込まず、必要になった時点で読み込むことができます。

○サンプルコード3:アニメーションの実装

requestAnimationFrameを使用したアニメーションの実装例を紹介します。

この方法を使用することで、スムーズなアニメーションを実現できます。

function animate() {
  const element = document.querySelector('.box');
  let start;

  function step(timestamp) {
    if (!start) start = timestamp;
    const progress = timestamp - start;

    element.style.transform = 'translateX(' + Math.min(progress / 10, 200) + 'px)';

    if (progress < 2000) {
      requestAnimationFrame(step);
    }
  }

  requestAnimationFrame(step);
}

animate();

このコードでは、.box要素を右に移動させるアニメーションを実装しています。

requestAnimationFrameを使用することで、ブラウザの描画タイミングに合わせて滑らかなアニメーションを実現しています。

○サンプルコード4:非同期APIリクエスト

async/awaitを使用した非同期APIリクエストの例を紹介します。

この方法を使用することで、非同期処理を同期的に書くことができ、コードの可読性が向上します。

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}

fetchData();

このコードでは、fetch関数を使用してAPIからデータを取得し、取得したデータをJSONとしてパースしています。

async/awaitを使用することで、非同期処理をより直感的に記述できます。

また、try/catch文を使用してエラーハンドリングも行っています。

●注意点と対処法

遅延実行を利用する際には、いくつかの注意点があります。

この点を理解し、適切に対処することで、より効果的に遅延実行を活用できます。

  1. setTimeoutsetIntervalの実行タイミングは、ブラウザの状況によっては遅れることがあります。特に、ブラウザが他の処理でビジー状態の場合や、ページがバックグラウンド状態の場合に顕著です。この問題に対処するには、requestAnimationFrameを使用することで、ブラウザの描画タイミングに合わせて処理を実行できます。
  2. setTimeoutsetIntervalで登録したコールバック関数は、グローバルスコープで実行されます。そのため、関数内でthisを使用した場合、意図しない挙動が発生する可能性があります。この問題を解決するには、アロー関数を使用することで、thisの値を保持できます。
  3. スクロールやリサイズなど、高頻度で発生するイベントの処理は、パフォーマンスに悪影響を与える可能性があります。この問題に対処するには、デバウンスやスロットリングを利用して、処理の頻度を制限することができます。
  4. async/awaitを使用して非同期処理を実行する際、エラー処理を適切に行わないと、Promiseの中で発生したエラーがキャッチされずにプログラムが停止する可能性があります。この問題を解決するには、try-catch文を使用してエラー処理を適切に行うようにしましょう。

●カスタマイズ方法

遅延実行を使用したコードをプロジェクトの要件に合わせてカスタマイズする際には、次のポイントを参考にしてください。

各サンプルコードを基に、具体的なカスタマイズ方法を解説します。

○サンプルコード1:スクロールイベントの最適化

スクロールイベントの最適化におけるデバウンスの遅延時間は、アプリケーションの要件に応じて調整できます。

遅延時間を短くすると、イベントの反応が早くなりますが、処理の頻度が高くなるためパフォーマンスに影響が出る可能性があります。

逆に、遅延時間を長くすると、パフォーマンスは向上しますが、ユーザー体験が低下する可能性があります。

例えば、スクロール位置に応じて要素を表示・非表示にする処理では、遅延時間を100ミリ秒程度に設定することで、スムーズな動作と適度なパフォーマンスのバランスを取ることができます。

let scrollTimer;
const scrollDelay = 100; // ミリ秒単位で遅延時間を設定

window.addEventListener('scroll', function() {
  clearTimeout(scrollTimer);
  scrollTimer = setTimeout(function() {
    // スクロール後の処理をここに記述
    updateElementsVisibility();
  }, scrollDelay);
});

function updateElementsVisibility() {
  // 画面内の要素の表示・非表示を更新する処理
}

○サンプルコード2:画像の遅延読み込み

画像の遅延読み込み(レイジーロード)実装では、読み込みのトリガーとなる要素や属性を変更することで、様々なニーズに対応できます。

例えば、data-src属性の代わりにdata-srcsetdata-background-image属性を使用することで、レスポンシブ画像や背景画像にも対応できます。

また、Intersection Observer APIを使用することで、よりパフォーマンスの高い実装が可能です。

document.addEventListener('DOMContentLoaded', function() {
  const lazyImages = [].slice.call(document.querySelectorAll('img.lazy'));

  if ('IntersectionObserver' in window) {
    let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          let lazyImage = entry.target;
          lazyImage.src = lazyImage.dataset.src;
          lazyImage.srcset = lazyImage.dataset.srcset;
          lazyImage.classList.remove('lazy');
          lazyImageObserver.unobserve(lazyImage);
        }
      });
    });

    lazyImages.forEach(function(lazyImage) {
      lazyImageObserver.observe(lazyImage);
    });
  } else {
    // Intersection Observer APIがサポートされていない場合のフォールバック
    // 従来のスクロールイベントベースの実装を使用
  }
});

○サンプルコード3:アニメーションの実装

requestAnimationFrameを使用したアニメーション実装では、アニメーションの動きや持続時間、イージング関数などをカスタマイズすることで、より洗練された効果を得ることができます。

例えば、イージング関数を追加して、よりスムーズな動きを実現できます。

function animate() {
  const element = document.querySelector('.box');
  let start;

  function easeOutQuad(t) {
    return t * (2 - t);
  }

  function step(timestamp) {
    if (!start) start = timestamp;
    const progress = timestamp - start;
    const duration = 2000; // アニメーション全体の持続時間

    if (progress < duration) {
      const easeProgress = easeOutQuad(progress / duration);
      element.style.transform = `translateX(${easeProgress * 200}px)`;
      requestAnimationFrame(step);
    }
  }

  requestAnimationFrame(step);
}

animate();

○サンプルコード4:非同期APIリクエスト

非同期APIリクエストの実装では、エラーハンドリングやリトライロジックの追加、複数のAPIエンドポイントの同時呼び出しなど、様々なカスタマイズが可能です。

例えば、タイムアウト処理とリトライロジックを追加することで、より堅牢な実装ができます。

async function fetchDataWithRetry(url, retries = 3, timeout = 5000) {
  for (let i = 0; i < retries; i++) {
    try {
      const controller = new AbortController();
      const id = setTimeout(() => controller.abort(), timeout);

      const response = await fetch(url, { signal: controller.signal });
      clearTimeout(id);

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      return await response.json();
    } catch (error) {
      if (i === retries - 1) throw error;
      console.log(`Attempt ${i + 1} failed. Retrying...`);
      await new Promise(resolve => setTimeout(resolve, 1000)); // 1秒待機してリトライ
    }
  }
}

async function fetchData() {
  try {
    const data = await fetchDataWithRetry('https://api.example.com/data');
    console.log(data);
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}

fetchData();

このようにカスタマイズすることで、ネットワークの不安定さやサーバーの一時的な障害にも対応できる、より信頼性の高い実装が可能となります。

まとめ

本記事では、JavaScriptにおける遅延実行の概念、主要な実装方法、実践的なサンプルコード、そして注意点とカスタマイズ方法について詳しく解説しました。

遅延実行は、ウェブアプリケーションのパフォーマンスとユーザーエクスペリエンスを向上させるための重要な技術です。

遅延実行の技術を効果的に活用することで、より高速で、より応答性が高く、そしてユーザーにとってより使いやすいウェブアプリケーションを開発することができます。

この知識を基に、皆さんのプロジェクトで遅延実行を活用し、素晴らしいウェブ体験を創造してみましょう。