JavaScript の実行を最適化する

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

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 を使用するようにパッチを適用できます。これは強くおすすめします。

複雑さを軽減する、またはウェブワーカーを使用する

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

JavaScript の実行タイミングと実行時間については、戦術的に判断する必要があります。たとえば、スクロールなどのアニメーションの場合は、JavaScript を 3 ~ 4 ms の範囲に抑えることが理想的です。それ以上長くなると、時間がかかりすぎる可能性があります。アイドル状態の場合は、所要時間についてよりリラックスして構えることができます。

多くの場合、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...
});

すべての作業がこのモデルに適合するわけではありません。ウェブワーカーは DOM にアクセスできません。メインスレッドで処理を行う必要がある場合は、バッチ処理アプローチを検討してください。このアプローチでは、大きなタスクをマイクロタスクに分割し、各タスクの実行時間を数ミリ秒以内に抑え、各フレームの requestAnimationFrame ハンドラ内で実行します。

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

JavaScript の「フレーム税」を確認する

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

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

Chrome DevTools でのパフォーマンス レコーディング

[Main] セクションには、JavaScript 呼び出しのフレームグラフが表示されます。これにより、呼び出された関数と各関数の所要時間を正確に分析できます。

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

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

JavaScript を微調整しない

ブラウザがあるバージョンの処理を別のバージョンの処理よりも 100 倍速く実行できることを知るのは面白いかもしれませんが、要素の offsetTop をリクエストする方が getBoundingClientRect() を計算するよりも速いなどです。しかし、ほとんどの場合、このような関数はフレームごとに数回しか呼び出されないため、JavaScript のパフォーマンスのこの側面に焦点を当てても、通常は労力の無駄になります。通常はミリ秒単位でしか節約できません。

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

つまり、マイクロ最適化は通常、構築しているアプリケーションの種類にマッピングされないため、非常に注意する必要があります。