????????????????????????????????????????????????(原始问题) h2>
事实上,执行performance test at jsperf 并检查控制台中的一些内容。对于研究,使用the website irt.org。下面是所有这些源代码的集合,以及底部的示例函数。
╔═══════════════╦══════╦════════════════════════ ══════════╦═════════╦══════════╗
║ 方法 ║Concat║slice&push.apply ║ push.apply x2 ║ ForLoop ║Spread ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║ mOps/秒 ║179 ║104 ║ 76 ║ 81 ║28 ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║稀疏数组║YES! ║只有切片 ║ 没有 ║ Maybe2 ║no ║
║保持稀疏║║数组(第一个参数)║║║║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║ 支持 ║MSIE 4║MSIE 5.5 ║ MSIE 5.5 ║ MSIE 4 ║Edge 12 ║
║ (source) ║NNav 4║NNav 4.06 ║ NNav 4.06 ║ NNav 3 ║MSIE NNav ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║类似阵列的行为║no ║只有被推║ YES! ║ 是的! ║如果有║
║像一个数组 ║ ║array (第二个参数) ║ ║ ║iterator1║
╚═══════════════╩══════╩═════════════════╩════════ ═══════╩═════════╩══════════╝
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;
}
如上所示,我认为 Concat 几乎始终是兼顾性能和保持备用阵列稀疏性的方法。然后,对于类似数组(例如 DOMNodeLists,如document.body.children),我建议使用 for 循环,因为它既是性能第二高的方法,也是唯一保留稀疏数组的其他方法。下面,我们将快速回顾一下稀疏数组和类似数组的含义,以消除混淆。
??? ??????
起初,有些人可能认为这是侥幸,浏览器供应商最终会设法优化 Array.prototype.push,使其速度足以击败 Array.prototype.concat。错误的! Array.prototype.concat 总是会更快(至少在原则上),因为它是对数据的简单复制粘贴。下面是一个简化的 32 位数组实现可能是什么样子的直观图(请注意实际实现要复杂得多)
字节║数据在这里
═════╬═══════════
0x00 ║ int nonNumericPropertiesLength = 0x00000000
0x01 ║ 同上
0x02 ║ 同上
0x03 ║ 同上
0x00 ║ 整数长度 = 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 代表 Array.prototype.concat 而#2 代表 Array.prototype.push.apply。让我们用一个类似的 JSperf 来测试它,不同之处仅在于它测试的是稀疏数组上的方法,而不是实心数组。可以找到right here。
因此,我认为这个特定用例的性能未来不在于 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: "bar", 长度:2}
document.body.children
新的 Uint8Array(3)
- 这类似于数组,因为虽然它是一个 (n)(类型化)数组,但将其强制为数组会更改构造函数。
(function(){返回参数})()
观察使用一种将类似数组的对象强制转换为 slice 等数组的方法会发生什么。
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}() ));
-
注意:出于性能考虑,对函数参数调用 slice 是不好的做法。
观察使用不将类似数组的对象强制转换为 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}() ));