javascript数组在推送新元素时如何工作?

时间:2021-11-15 07:10:06

I tested this code in Chrome / Firefox :

我在Chrome / Firefox中测试了这段代码:

console.time('simple push');
var arr0 = [];
for(var i =0; i < 1000000; i++){
    arr0.push(i);
}
console.timeEnd('simple push');
console.time('set length and push');
var arr1 = [];
arr1.length=1000000;
for(var j =0; j < 1000000; j++){
    arr1[j]=j;
}
console.timeEnd('set length and push');
console.time('new Array push');
var arr2 = new Array(1000000);
for(var k =0; k < 1000000; k++){
    arr2[k]=k;
}
console.timeEnd('new Array push');

Chrome 13 Result

simple push:59ms
set length and push:192ms
new Array push:187ms

简单推送:59ms设置长度和推送:192ms新阵列推送:187ms

Firefox 4 Result

simple push:76ms
set length and push:44ms
new Array push:40ms

简单推送:76ms设置长度和推送:44ms新阵列推送:40ms

My doubt

So new Array operation is definitely the slowest, but I wanna know why?
Why set length behaves different in Chrome and Firefox, it seems preallocated memory doesn't works well in Chrome?

所以新阵列操作肯定是最慢的,但我想知道为什么?为什么设置长度在Chrome和Firefox中表现不同,看来预分配的内存在Chrome中效果不佳?

Update

I updated Chrome and FF results.

我更新了Chrome和FF结果。

2 个解决方案

#1


6  

Why is the new Array(N) the slowest?

为什么新阵列(N)最慢?

console.log(arr0.length);
console.log(arr1.length);
console.log(arr2.length);

1000000
1000000
2000000

#2


0  

As you have been already made aware of, your testcases were flawed. Here is a jsperf test case which uses assignment in all methods.

正如您已经意识到的那样,您的测试用例存在缺陷。这是一个jsperf测试用例,它在所有方法中使用赋值。

The speed is the same for all of them in Firefox 6 (~50,000 op/s). In Chrome 13 however, setting the array length beforehand results in a huge speed improvement (~80,000 op/s vs. ~400.000 op/s). To find the reason for that, one would have to have a look at the source code of Chrome's JavaScript engine.

Firefox 6中的所有速度都相同(~5000 op / s)。然而,在Chrome 13中,预先设置阵列长度可以大大提高速度(~80,000 op / s vs.~400.000 op / s)。要找到原因,就必须查看Chrome JavaScript引擎的源代码。

You asked what happens on .push() and new Array. The specification describes was should happen:

你问过.push()和new Array会发生什么。规范描述应该发生:

15.4.2.2 new Array (len)

The [[Prototype]] internal property of the newly constructed object is set to the original Array prototype object, the one that is the initial value of Array.prototype (15.4.3.1). The [[Class]] internal property of the newly constructed object is set to "Array". The [[Extensible]] internal property of the newly constructed object is set to true.

新构造对象的[[Prototype]]内部属性设置为原始Array原型对象,即Array.prototype(15.4.3.1)的初始值。新构造的对象的[[Class]]内部属性设置为“Array”。新构造的对象的[[Extensible]]内部属性设置为true。

If the argument len is a Number and ToUint32(len) is equal to len, then the length property of the newly constructed object is set to ToUint32(len). If the argument len is a Number and ToUint32(len) is not equal to len, a RangeError exception is thrown.

如果参数len是Number且ToUint32(len)等于len,则新构造的对象的length属性设置为ToUint32(len)。如果参数len是Number且ToUint32(len)不等于len,则抛出RangeError异常。

If the argument len is not a Number, then the length property of the newly constructed object is set to 1 and the 0 property of the newly constructed object is set to len with attributes {[[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}..

如果参数len不是Number,则新构造的对象的length属性设置为1,新构造的对象的0属性设置为len,属性为{[[Writable]]:true,[[Enumerable] ]:true,[[Configurable]]:true} ..

15.4.4.7 Array.prototype.push ( [ item1 [ , item2 [ , … ] ] ] )

The arguments are appended to the end of the array, in the order in which they appear. The new length of the array is returned as the result of the call.

参数按照它们出现的顺序附加到数组的末尾。作为调用的结果,返回数组的新长度。

When the push method is called with zero or more arguments item1, item2, etc., the following steps are taken:

当使用零个或多个参数item1,item2等调用push方法时,将执行以下步骤:

  1. Let O be the result of calling ToObject passing the this value as the argument.
  2. 设O是调用ToObject传递此值作为参数的结果。
  3. Let lenVal be the result of calling the [[Get]] internal method of O with argument "length".
  4. 设lenVal是使用参数“length”调用O的[[Get]]内部方法的结果。
  5. Let n be ToUint32(lenVal).
  6. 设n为ToUint32(lenVal)。
  7. Let items be an internal List whose elements are, in left to right order, the arguments that were passed to this function invocation.
  8. 令items为内部List,其元素按从左到右的顺序排列为传递给此函数调用的参数。
  9. Repeat, while items is not empty
    1. Remove the first element from items and let E be the value of the element.
    2. 从项中删除第一个元素,让E为元素的值。
    3. Call the [[Put]] internal method of O with arguments ToString(n), E, and true.
    4. 使用参数ToString(n),E和true调用O的[[Put]]内部方法。
    5. Increase n by 1.
    6. 将n增加1。
  10. 重复,而items不为空从项中删除第一个元素,让E为元素的值。使用参数ToString(n),E和true调用O的[[Put]]内部方法。将n增加1。
  11. Call the [[Put]] internal method of O with arguments "length", n, and true.
  12. 使用参数“length”,n和true调用O的[[Put]]内部方法。
  13. Return n.
  14. 返回

That said, the implementation can be different. As you saw from the difference in Firefox and Chrome, Chrome seems to optimize the array structure internally.

也就是说,实施可能会有所不同。正如您从Firefox和Chrome的不同之处看到的那样,Chrome似乎在内部优化了阵列结构。

#1


6  

Why is the new Array(N) the slowest?

为什么新阵列(N)最慢?

console.log(arr0.length);
console.log(arr1.length);
console.log(arr2.length);

1000000
1000000
2000000

#2


0  

As you have been already made aware of, your testcases were flawed. Here is a jsperf test case which uses assignment in all methods.

正如您已经意识到的那样,您的测试用例存在缺陷。这是一个jsperf测试用例,它在所有方法中使用赋值。

The speed is the same for all of them in Firefox 6 (~50,000 op/s). In Chrome 13 however, setting the array length beforehand results in a huge speed improvement (~80,000 op/s vs. ~400.000 op/s). To find the reason for that, one would have to have a look at the source code of Chrome's JavaScript engine.

Firefox 6中的所有速度都相同(~5000 op / s)。然而,在Chrome 13中,预先设置阵列长度可以大大提高速度(~80,000 op / s vs.~400.000 op / s)。要找到原因,就必须查看Chrome JavaScript引擎的源代码。

You asked what happens on .push() and new Array. The specification describes was should happen:

你问过.push()和new Array会发生什么。规范描述应该发生:

15.4.2.2 new Array (len)

The [[Prototype]] internal property of the newly constructed object is set to the original Array prototype object, the one that is the initial value of Array.prototype (15.4.3.1). The [[Class]] internal property of the newly constructed object is set to "Array". The [[Extensible]] internal property of the newly constructed object is set to true.

新构造对象的[[Prototype]]内部属性设置为原始Array原型对象,即Array.prototype(15.4.3.1)的初始值。新构造的对象的[[Class]]内部属性设置为“Array”。新构造的对象的[[Extensible]]内部属性设置为true。

If the argument len is a Number and ToUint32(len) is equal to len, then the length property of the newly constructed object is set to ToUint32(len). If the argument len is a Number and ToUint32(len) is not equal to len, a RangeError exception is thrown.

如果参数len是Number且ToUint32(len)等于len,则新构造的对象的length属性设置为ToUint32(len)。如果参数len是Number且ToUint32(len)不等于len,则抛出RangeError异常。

If the argument len is not a Number, then the length property of the newly constructed object is set to 1 and the 0 property of the newly constructed object is set to len with attributes {[[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}..

如果参数len不是Number,则新构造的对象的length属性设置为1,新构造的对象的0属性设置为len,属性为{[[Writable]]:true,[[Enumerable] ]:true,[[Configurable]]:true} ..

15.4.4.7 Array.prototype.push ( [ item1 [ , item2 [ , … ] ] ] )

The arguments are appended to the end of the array, in the order in which they appear. The new length of the array is returned as the result of the call.

参数按照它们出现的顺序附加到数组的末尾。作为调用的结果,返回数组的新长度。

When the push method is called with zero or more arguments item1, item2, etc., the following steps are taken:

当使用零个或多个参数item1,item2等调用push方法时,将执行以下步骤:

  1. Let O be the result of calling ToObject passing the this value as the argument.
  2. 设O是调用ToObject传递此值作为参数的结果。
  3. Let lenVal be the result of calling the [[Get]] internal method of O with argument "length".
  4. 设lenVal是使用参数“length”调用O的[[Get]]内部方法的结果。
  5. Let n be ToUint32(lenVal).
  6. 设n为ToUint32(lenVal)。
  7. Let items be an internal List whose elements are, in left to right order, the arguments that were passed to this function invocation.
  8. 令items为内部List,其元素按从左到右的顺序排列为传递给此函数调用的参数。
  9. Repeat, while items is not empty
    1. Remove the first element from items and let E be the value of the element.
    2. 从项中删除第一个元素,让E为元素的值。
    3. Call the [[Put]] internal method of O with arguments ToString(n), E, and true.
    4. 使用参数ToString(n),E和true调用O的[[Put]]内部方法。
    5. Increase n by 1.
    6. 将n增加1。
  10. 重复,而items不为空从项中删除第一个元素,让E为元素的值。使用参数ToString(n),E和true调用O的[[Put]]内部方法。将n增加1。
  11. Call the [[Put]] internal method of O with arguments "length", n, and true.
  12. 使用参数“length”,n和true调用O的[[Put]]内部方法。
  13. Return n.
  14. 返回

That said, the implementation can be different. As you saw from the difference in Firefox and Chrome, Chrome seems to optimize the array structure internally.

也就是说,实施可能会有所不同。正如您从Firefox和Chrome的不同之处看到的那样,Chrome似乎在内部优化了阵列结构。