たとえば、スムーズなアニメーションが重要なブラウザー アプリを作成しているとします。ガベージ コレクションが実行を長時間ブロックしてフリーズを引き起こす可能性があることはわかっているため、作成するガベージの量を最小限に抑える必要があります。ガベージを最小限に抑えるには、メインのアニメーション ループの実行中にメモリの割り当てを避ける必要があります。
しかし、その実行パスにはループが散らばっています。
var i = things.length; while (i--) { /* stuff */ }
for (var i = 0, len = things.length; i < len; i++) { /* stuff */ }
そして、それらのvar
文のallocate memoryは、ガベージ コレクターが削除する可能性のあるメモリを割り当てる可能性がありますが、これは避けたいことです。
では、JavaScript でループ構造を記述して、それぞれにメモリを割り当てるのを避けるには、どのような戦略がよいのでしょうか? 長所と短所をリストした一般的な解決策を探しています。
私が思いついたアイデアは以下の3つです。
1.)インデックスと長さの「トップレベル」変数を宣言します。どこでも再利用
app.i
先頭にandを宣言app.length
して、何度も再利用できます。
app.i = things.length; while (app.i--) { /* stuff */ }
for (app.i = 0; app.i < app.length; app.i++) { /* stuff */ }
長所:実装が簡単です。短所:プロパティの逆参照によるパフォーマンスの低下は、ピュロスの勝利を意味する可能性があります。誤ってプロパティを誤用/破壊し、バグを引き起こす可能性があります。
2.)配列の長さがわかっている場合は、ループしないでください -- 展開します
配列に特定の数の要素があることが保証される場合があります。事前に長さがわかっている場合は、プログラムでループを手動で巻き戻すことができます。
doSomethingWithThing(things[0]);
doSomethingWithThing(things[1]);
doSomethingWithThing(things[2]);
長所:効率的。短所:実際にはめったに可能ではありません。醜い?変わるの面倒?
3.)ファクトリ パターンを使用してクロージャーを活用する
コレクションの要素に対してアクションを実行する関数である「ルーパー」を返すファクトリ関数を作成します ( a la _.each
)。ルーパーは、作成されたクロージャー内のインデックス変数と長さ変数へのプライベート参照を保持します。ルーパーは呼び出されるたびにリセットする必要がi
あります。length
function buildLooper() {
var i, length;
return function(collection, functionToPerformOnEach) { /* implement me */ };
}
app.each = buildLooper();
app.each(things, doSomethingWithThing);
長所:より機能的で、より慣用的ですか? 短所:関数呼び出しはオーバーヘッドを追加します。クロージャ アクセスは、オブジェクト ルックアップよりも遅いことが示されています。