JavaScript の実行を最適化する

JavaScript は多くの場合、視覚的な変化を引き起こします。それはスタイル操作を通じて直接行われることもあれば、データの検索や並べ替えのように計算によって視覚的な変化が生じることもあります。タイミングの悪い JavaScript や実行時間の長い JavaScript は、パフォーマンスの問題の一般的な原因です。このような影響は可能な限り最小限に抑える必要があります。

ポール ルイス
Paul Lewis

JavaScript は多くの場合、視覚的な変化を引き起こします。場合によっては、スタイル操作を通じて直接行われる場合もあれば、データの検索や並べ替えなど、計算によって視覚的変化がもたらされる場合もあります。タイミングの悪い JavaScript や長時間実行される JavaScript は、パフォーマンスの問題の一般的な原因です。このような影響は可能な限り最小限に抑える必要があります。

記述する JavaScript は実際に実行されるコードのようなものではないため、JavaScript のパフォーマンス プロファイリングは技術を要する可能性があります。最新のブラウザでは、JIT コンパイラやあらゆる種類の最適化とトリックを使用して、可能な限り高速な実行を実現しようとしていますが、これによってコードの力学は大きく変化しています。

とはいえ、それでも、アプリで JavaScript を適切に実行できるようにするためにできることがいくつかあります。

概要

  • 視覚的な更新には setTimeout または setInterval を使用せず、代わりに常に requestAnimationFrame を使用してください。
  • 長時間実行 JavaScript をメインスレッドから Web Worker に移行する。
  • マイクロタスクを使用して、複数のフレームにわたって DOM を変更します。
  • Chrome DevTools のタイムラインと JavaScript プロファイラを使用して、JavaScript の影響を評価します。

requestAnimationFrame を使用して見た目を変更する

画面上で視覚的に変化が生じている場合、ブラウザに適したタイミング(フレームの先頭の直後)に処理を行う必要があります。フレームの開始時に JavaScript が実行されることを保証する唯一の方法は、requestAnimationFrame を使用することです。

/**
    * If run as a requestAnimationFrame callback, this
    * will be run at the start of the frame.
    */
function updateScreen(time) {
    // Make visual updates here.
}

requestAnimationFrame(updateScreen);

フレームワークまたはサンプルでは setTimeout または setInterval を使用して、アニメーションなどの視覚的な変更を行うことができますが、問題は、コールバックがフレーム内のいずれかのポイント(最後など)で実行されるため、フレームを見逃してジャンクが発生することがよくあります。

setTimeout(ブラウザがフレームを見逃す原因となる)

実際、jQuery では animate の動作に setTimeout を使用していました。バージョン 3 では requestAnimationFrame を使用するように変更されました。古いバージョンの jQuery を使用している場合は、requestAnimationFrame を使用するようにパッチを適用することを強くおすすめします。

複雑さを軽減するか、Web Worker を使用する

JavaScript は、スタイルの計算、レイアウト、そして多くの場合はペイントとともに、ブラウザのメインスレッド上で実行されます。JavaScript が長時間実行されると、このような他のタスクがブロックされ、フレームを読み取れなくなる可能性があります。

JavaScript をいつ、どのくらいの時間実行するかについては、戦術に決める必要があります。たとえば、スクロールのようなアニメーションを使用する場合は、JavaScript を 3 ~ 4 ミリ秒以内に抑えるのが理想的です。それより長くかかると、時間がかかりすぎる可能性があります。アイドル状態の場合は、時間に余裕を持たせることができます。

DOM アクセスが不要な場合など、多くの場合、純粋な計算処理を Web Worker に移行できます。並べ替えや検索などのデータ操作や走査は、多くの場合、読み込みやモデル生成と同様に、このモデルに適しています。

var dataSortWorker = new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);

// The main thread is now free to continue working on other things...

dataSortWorker.addEventListener('message', function(evt) {
    var sortedData = evt.data;
    // Update data on screen...
});

すべての作業がこのモデルに適合するわけではありません。Web Worker は DOM にアクセスできません。処理をメインスレッドで実行する必要がある場合は、バッチ処理のアプローチを検討してください。大きなタスクを複数のマイクロタスクに分割し、それぞれが数ミリ秒もかからないマイクロタスクに分割して、各フレームで requestAnimationFrame ハンドラ内で実行します。

このアプローチでは UX と UI に影響が生じるため、進行状況またはアクティビティ インジケーターを使用して、タスクが処理中であることをユーザーがわかるようにする必要があります。いずれにせよ、アプリのメインスレッドを解放できるため、ユーザーの操作に対する応答性を維持できます。

JavaScript の「フレーム 税金」を把握する

フレームワーク、ライブラリ、または独自のコードを評価する際は、JavaScript コードをフレームごとに実行する場合のコストを評価することが重要です。これは、遷移やスクロールなど、パフォーマンスが重要なアニメーション処理を行うときに特に重要です。

JavaScript の費用を測定するには、Chrome DevTools の [パフォーマンス] パネルを使うのが最適な方法です。通常、次のような下位レベルのレコードが取得されます。

Chrome DevTools のパフォーマンスの記録

[Main] セクションには JavaScript 呼び出しのフレーム チャートが表示されるため、呼び出された関数とその所要時間を正確に分析できます。

この情報を活用することで、アプリケーションにおける JavaScript のパフォーマンスへの影響を評価し、関数の実行に時間がかかりすぎるホットスポットを特定して修正できるようになります。前述のように、長時間実行されている JavaScript を削除するか、それができない場合は Web Worker に移動し、メインスレッドを解放して他のタスクを続行できるようにしてください。

パフォーマンス パネルの使用方法については、ランタイム パフォーマンスの分析を開始するをご覧ください。

JavaScript の細かい最適化を避ける

ブラウザがあるバージョンのものを別のものより 100 倍速く実行できると知っていても、たとえば要素の offsetTop をリクエストする方が getBoundingClientRect() を計算するよりも高速です。しかし、ほとんどの場合、このような関数はフレームごとに少ない回数しか呼び出すことはないため、JavaScript のパフォーマンスのこの側面に注力するのは無駄な作業です。通常、節約できるのは数ミリ秒のみです。

ゲームや計算コストの高いアプリケーションを作成している場合、通常は大量の計算を 1 つのフレームに収めるため、このガイダンスの例外となる可能性があります。その場合は、すべてが役立ちます。

つまり、マイクロ最適化は通常、構築するアプリケーションの種類には対応しないため、慎重に行う必要があります。