𝗥𝗲𝘀𝗲𝗮𝗿𝗰𝗵𝗔𝗻𝗱𝗥𝗲𝘀𝘂𝗹𝘁𝘀
事実については、jsperfでパフォーマンステストを実行し、コンソールでいくつかのことを確認しています。調査にはウェブサイトirt.orgを使用しています。以下は、これらすべてのソースをまとめたものと、下部にある関数の例です。
╔═══════════════╦══════╦═════════════════╦════════ ═══════╦═════════╦══════════╗
║メソッド║Concat║slice&push.apply║push.apply x2║ForLoop║Spread║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║mOps /秒║179║104║76║81║28║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║スパース配列║はい!║Onlyスライス║║なしたぶん2 ║no║
║スパースを維持║║配列(1番目の引数)║║║║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║サポート║MSIE4║MSIE5.5║MSIE 5.5║MSIE 4║Edge12║
║(ソース)║NNav4║NNav4.06║NNav 4.06║NNav 3║ MSIE NNav ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║配列のような行為║いいえ║プッシュされた║のみ║はい!║はい!have持っている場合║
アレイ║║array(第2引数)║║║iterator║like 1 ║
╚═══════════════╩══════╩═════════════════╩════════ ═══════╩═════════╩══════════╝
1配列のようなオブジェクトにSymbol.iteratorプロパティがない場合は、
拡散すると例外がスローされます。
2コードによって異なります。次のコード例「YES」は、スパース性を維持します。
function mergeCopyTogether(inputOne, inputTwo){
var oneLen = inputOne.length, twoLen = inputTwo.length;
var newArr = [], newLen = newArr.length = oneLen + twoLen;
for (var i=0, tmp=inputOne[0]; i !== oneLen; ++i) {
tmp = inputOne[i];
if (tmp !== undefined || inputOne.hasOwnProperty(i)) newArr[i] = tmp;
}
for (var two=0; i !== newLen; ++i, ++two) {
tmp = inputTwo[two];
if (tmp !== undefined || inputTwo.hasOwnProperty(two)) newArr[i] = tmp;
}
return newArr;
}
上で見たように、コンキャットはほとんどの場合、パフォーマンスとスペアアレイのスパース性を維持する機能の両方を実現する方法であると主張します。次に、配列のような(DOMNodeListsのようなdocument.body.children
)場合は、forループを使用することをお勧めします。これは、2番目にパフォーマンスが高く、疎な配列を保持する他の唯一のメソッドだからです。以下では、混乱を解消するために、スパース配列と配列のようなものが何を意味するかを簡単に説明します。
𝗧𝗵𝗲𝗙𝘂𝘁𝘂𝗿𝗲
最初は、これはまぐれであり、ブラウザベンダは最終的にArray.prototype.pushを最適化して、Array.prototype.concatに勝る十分な速度になると考える人もいます。違う!Array.prototype.concatは、データを単純にコピーして貼り付けるだけなので、(少なくとも原則として)常に高速になります。以下は、32ビット配列の実装がどのように見えるかを示す簡略化された説得力のある視覚的な図です(実際の実装はLOTがより複雑であることに注意してください)。
バイト║ここにデータ
═════╬═══════════
0x00║int nonNumericPropertiesLength = 0x00000000
0x01║同上
0x02║同上
0x03║同上
0x00║intの長さ= 0x00000001
0x01║同上
0x02║同上
0x03║同上
0x00║int valueIndex = 0x00000000
0x01║同上
0x02║同上
0x03║同上
0x00║int valueType = JS_PRIMITIVE_NUMBER
0x01║同上
0x02║同上
0x03║同上
0x00║uintptr_t valuePointer = 0x38d9eb60(またはメモリ内の任意の場所)
0x01║同上
0x02║同上
0x03║同上
上記のように、そのようなものをコピーするために必要なことは、バイトごとにコピーするのとほとんど同じくらい簡単です。Array.prototype.push.applyを使用すると、データを単純にコピーして貼り付けるだけではありません。「.apply」は、配列内の各インデックスを確認し、それをArray.prototype.pushに渡す前に引数のセットに変換する必要があります。次に、Array.prototype.pushは毎回さらに多くのメモリを追加で割り当てる必要があり、(一部のブラウザ実装では)スパース性のために位置検索データを再計算することもできます。
それを考える別の方法はこれです。ソースアレイ1は、ホチキス止めされた紙の大きなスタックです。ソースアレイ2も、別の大きな紙のスタックです。あなたがする方が速くなりますか
- ストアに移動し、各ソースアレイのコピーに必要な十分な紙を購入します。次に、用紙の各ソースアレイスタックをコピー機に通して、2つのコピーを一緒にホチキス止めします。
- ストアに行って、最初のソースアレイの1つのコピーに十分な紙を購入します。次に、ソースアレイを新しい用紙に手動でコピーし、空白のまばらなスポットを確実に埋めます。次に、店に戻って、2番目のソースアレイ用に十分な紙を購入します。次に、2番目のソースアレイを通過し、コピーに空白のギャップがないことを確認しながらコピーします。次に、コピーしたすべての用紙を一緒にホチキス止めします。
上記の類推では、オプション#1はArray.prototype.concatを表し、オプション#2はArray.prototype.push.applyを表します。これを、類似のJSperfでテストしてみましょう。これは、ソリッド配列ではなく、スパース配列に対してメソッドをテストする点だけが異なります。ここで見つけることができます。
したがって、この特定のユースケースのパフォーマンスの将来はArray.prototype.pushではなく、Array.prototype.concatにあると私は主張します。
𝗖𝗹𝗮𝗿𝗶𝗳𝗶𝗰𝗮𝘁𝗶𝗼𝗻𝘀
𝗦𝗽𝗮𝗿𝗲𝗔𝗿𝗿𝗮𝘆𝘀
配列の特定のメンバーが単に見つからない場合。例えば:
// This is just as an example. In actual code,
// do not mix different types like this.
var mySparseArray = [];
mySparseArray[0] = "foo";
mySparseArray[10] = undefined;
mySparseArray[11] = {};
mySparseArray[12] = 10;
mySparseArray[17] = "bar";
console.log("Length: ", mySparseArray.length);
console.log("0 in it: ", 0 in mySparseArray);
console.log("arr[0]: ", mySparseArray[0]);
console.log("10 in it: ", 10 in mySparseArray);
console.log("arr[10] ", mySparseArray[10]);
console.log("20 in it: ", 20 in mySparseArray);
console.log("arr[20]: ", mySparseArray[20]);
または、JavaScriptを使用すると、スペア配列を簡単に初期化できます。
var mySparseArray = ["foo",,,,,,,,,,undefined,{},10,,,,,"bar"];
𝗔𝗿𝗿𝗮𝘆-𝗟𝗶𝗸𝗲𝘀
配列のようなものは、少なくともlength
プロパティを持つオブジェクトですが、new Array
またはで初期化されていません[]
。たとえば、以下のオブジェクトは配列のように分類されます。
{0:「foo」、1:「バー」、長さ:2}
document.body.children
新しいUint8Array(3)
- これは配列に似ています。これは、a(n)(型付き)配列ですが、配列に強制変換するとコンストラクターが変更されるためです。
(function(){戻り引数})()
配列のようなものをスライスのような配列に強制変換するメソッドを使用して何が起こるかを観察します。
var slice = Array.prototype.slice;
// For arrays:
console.log(slice.call(["not an array-like, rather a real array"]));
// For array-likes:
console.log(slice.call({0: "foo", 1: "bar", length:2}));
console.log(slice.call(document.body.children));
console.log(slice.call(new Uint8Array(3)));
console.log(slice.call( function(){return arguments}() ));
- 注:パフォーマンス上の理由から、関数の引数でスライスを呼び出すことはお勧めできません。
配列のようなものをconcatのような配列に強制しないメソッドを使用して何が起こるかを観察します。
var empty = [];
// For arrays:
console.log(empty.concat(["not an array-like, rather a real array"]));
// For array-likes:
console.log(empty.concat({0: "foo", 1: "bar", length:2}));
console.log(empty.concat(document.body.children));
console.log(empty.concat(new Uint8Array(3)));
console.log(empty.concat( function(){return arguments}() ));