为什么 arr = [] 比 arr = new Array() 更快?

147

我运行了这段代码并得到了下面的结果。我想知道为什么 [] 更快?

console.time('using[]')
for(var i=0; i<200000; i++){var arr = []};
console.timeEnd('using[]')

console.time('using new')
for(var i=0; i<200000; i++){var arr = new Array};
console.timeEnd('using new')
  • 使用 []:299毫秒
  • 使用 new:363毫秒

感谢 Raynos,这里是一个性能基准测试,用于比较定义变量的不同方式。

进入图片描述


5
你可能会对jsperf感兴趣。 - Pointy
11
基准测试 - Raynos
2
@kinakuta 不是的。它们都创建新的不相等的对象。我的意思是,[] 在源代码上等同于 new Array(),而不是从表达式返回的对象。 - Raynos
1
是的,这并不是非常重要。但我喜欢知道。 - Mohsen
由于字面符号表示法首先将其转换为临时对象,然后再对其进行操作,因此存在性能差异。 - Saksham
显示剩余9条评论
5个回答

194

进一步扩展之前的答案...

从一个通用编译器的视角来看,不考虑虚拟机特定的优化:

首先,我们进行词法分析阶段,其中我们对代码进行标记化。

例如,可以生成以下标记:

[]: ARRAY_INIT
[1]: ARRAY_INIT (NUMBER)
[1, foo]: ARRAY_INIT (NUMBER, IDENTIFIER)
new Array: NEW, IDENTIFIER
new Array(): NEW, IDENTIFIER, CALL
new Array(5): NEW, IDENTIFIER, CALL (NUMBER)
new Array(5,4): NEW, IDENTIFIER, CALL (NUMBER, NUMBER)
new Array(5, foo): NEW, IDENTIFIER, CALL (NUMBER, IDENTIFIER)

希望这能为你提供足够的可视化,以便你可以了解需要处理多少更多(或更少)的内容。

  1. 基于上述标记,我们知道ARRAY_INIT将始终产生一个数组。因此,我们只需创建一个数组并填充它即可。就歧义而言,词法分析阶段已经将ARRAY_INIT与对象属性访问器(例如obj [foo])或字符串/正则表达式字面量内部的括号(例如"foo [] bar"或/ [] /)区分开来

  2. 这很小,但我们还有更多的标记与new Array一起使用。此外,尚不完全清楚我们是否只想创建一个数组。我们看到“new”标记,但是“new”的是什么?然后我们看到IDENTIFIER标记,表示我们想要一个新的“Array”,但JavaScript VM通常不区分IDENTIFIER标记和用于“本机全局对象”的标记。因此...

  3. 每次遇到IDENTIFIER标记时,我们必须查找作用域链。Javascript VM为每个执行上下文包含一个“Activation object”,其中可能包含“arguments”对象、本地定义变量等。如果在Activation对象中找不到它,则开始查找作用域链,直到达到全局范围。如果没有找到,则抛出ReferenceError

  4. 找到变量声明后,我们调用构造函数。new Array 是一个隐式函数调用,并且经验法则是执行期间函数调用更慢(这就是为什么静态C/C++编译器允许“函数内联”的原因 - JS JIT引擎(如SpiderMonkey)必须即时完成这一点的原因)

  5. Array构造函数被重载。Array构造函数作为本机代码实现,因此它提供了一些性能增强,但仍需要检查参数长度并根据情况采取行动。此外,在仅提供一个参数的情况下,我们还需要进一步检查参数的类型。new Array(“foo”)会产生["foo"],而new Array(1)会产生[undefined]

所以简化所有这些:使用数组文字,VM知道我们想要一个数组;对于new Array ,VM需要使用额外的CPU周期来确定new Array 实际上做了什么。


难道不是由于分配开销,a = new Array(1000); for(from 0 to 999){a[i]=i} 比 a = []; for(from 0 to 999){a[i]=i} 更快吗? - martian17
刚刚创建了一个测试用例。在你预先知道数组大小的情况下,new Array(n)会更快。 https://jsperf.com/square-braces-vs-new-array - martian17

29

可能的一个原因是new ArrayArray上需要进行名称查找(您可以在作用域中使用该名称的变量),而[]则不需要。


4
检查参数也可能会起到贡献作用。 - Leonid
Array 接受一个名为 len 的参数和多个参数。而 [] 只接受多个参数。此外,Firefox 测试显示几乎没有区别。 - Raynos
我认为这是有一定道理的。在IIFE中运行OP的循环测试对性能有*(相对)*显著的影响。包括 var Array = window.Array 可以提高 new Array 测试的性能。 - user113716
我认为这不正确,因为这个代码块:console.time('more vars new'); for(var i=0; i<200000; i++){ var arr = new Array() }; console.timeEnd('more vars new'); more vars new: 390ms和这个代码块:console.time('more vars new'); var myOtherObject = {}, myOtherArray = []; for(var i=0; i<200000; i++){ var arr = new Array() }; console.timeEnd('more vars new'); more vars new: 369ms返回相同的时间。 - Mohsen

2
此外,有趣的是,如果预先知道数组的长度(元素将在创建后立即添加),则在最近的 Google Chrome 70+ 上使用具有指定长度的数组构造函数比使用 "[]" 这种方式要快得多。
  • "new Array(%ARR_LENGTH%)" - 快100%!

  • "[]" - 慢160-170%。

测试结果图表

测试可以在这里找到 - https://jsperf.com/small-arr-init-with-known-length-brackets-vs-new-array/2

注意:此结果在 Google Chrome v.70+ 上测试;在 Firefox v.70 和 IE 中,两种变体几乎相等。


2

好问题。第一个例子被称为数组字面量,它是许多开发人员首选的创建数组的方式。可能是由于检查new Array()调用的参数然后创建对象导致性能差异,而字面量直接创建数组。

相对较小的性能差异支持这一点。顺便说一下,您可以使用Object和对象字面量{}进行相同的测试。


1

这样做有些意义。

对象字面量使我们能够编写支持许多功能的代码,同时仍然使我们的代码实现者相对简单。无需直接调用构造函数或维护传递给函数的参数的正确顺序等。

http://www.dyn-web.com/tutorials/obj_lit.php


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接