JavaScriptの性能について整理しておく

JavaScriptのチューニングの実践を通して学んだことを整理しておく。
クライアントJavaScriptの高速化の肝はDOMアクセスと変数アクセスにある。
特にDOMアクセスのコストは格段に高いため、一番のチューニングポイントになる。

  • DOMアクセス
    • DOMアクセスの削減
    • DOMアクセスのセレクタの効率化
  • 変数アクセス

以下に詳細は記述するが、全てに通ずる重要な一つの考えは、「不要な探索をさせない」ということだ。

DOMアクセス

DOMアクセスのタイミング
  1. DOM要素の取得
  2. 取得したDOM要素への操作

上記 2. がくせ者で、取得したDOM要素のプロパティにアクセスするたびに、DOMに対して探索が再実行されてしまう。

つまり・・・下記の処理を記述した場合、処理Aと処理Bのコストは同じということか・・・??←検証宿題
var id1 = $('#id1');
id1.val(); // 処理A
$('#id1').val(); // 処理B
たしかに、 #id1 の val はいつ変化するかわからないため、毎回DOMを見に行く必要があるのは然りだ。

DOMアクセスの削減

複数回参照する値については、ローカル変数にキャッシュすることで無駄なアクセスを回避する。

DOMアクセスのセレクタの効率化

前回の日記「CSSの適用について、予想外の事実を知った - oknknicの日記」を参照のこと。

変数アクセス

変数へのアクセス時間は、対象の変数がスコープチェーンのどこに位置するかと、その変数の格納場所に依存する。
いずれの場合も、複数回アクセスする場合は、ローカル変数にキャッシュすることで性能改善が可能である。

スコープチェーン

スコープは、以下の順でスタックに積まれていく。各スコープについて、そのスコープから下方向にあるスタック列をスコープチェーンと呼ぶ。

  1. 【ページロード時】グローバルスコープ生成
  2. 【関数呼び出し時*】スコープ生成
  3. 【関数内でwithあるいはcatchを実行時*】スコープ生成

また関数には、それが定義された際のスコープチェーンにアクセスするため、内部プロパティ Scope に定義時のスコープチェーンが設定されている。

スコープアクセスの性能

ローカル変数への読み書きは、識別子の解決のためにスコープチェーンを辿る必要がないため、格段に高速である。
逆に、グローバルオブジェクトへのアクセスは最も低速である。
ただし、性能影響の規模は、下記検証サイトでも確認できるように、非常に小さいものである。

変数の格納場所(≒データ構造)

アクセスにコストを伴う格納場所には、配列とオブジェクトがある。

補足:性能改善の限界を越えたいとき

越えられない壁を越えなければならないときは、 setTimeout による遅延実行を検討する。
シングルスレッドであるJavaScriptは、実行するコードをキューで管理している。 setTimeout 関数を使って関数を実行すると、その関数の処理を現在実行中の処理の一部とするのではなく、別の処理としてエンキューすることができる。
このように処理の実行を分割することで、ユーザ処理を挟み込む間ができるため、ユーザ視点では性能が改善されたと感じる。

参考文献