在第一次调用中,数组类型是 PACKED_ELEMENTS,V8使用 inline cache(内嵌缓存)记住 each 函数是被一个特定元素类型调用的,在下一次调用时,V8会先检查数组类型是否是 PACKED_ELEMENTS,如果是,则复用上次生成的代码,否则会做更多的事情,如重新生成相应的代码;
第二次调用的时候,数组类型是 PACKED_DOUBLE_ELEMENTS,V8检查到出现了与缓存中不一样的数组类型,此时,V8将 each 函数中的数组标记为多态,接下来的每一次 each 调用都需要进行 PACKED_ELEMENTS 和 PACKED_DOUBLE_ELEMENTS 两种类型的检查,这会造成一些性能消耗;
第三次调用结束的时候,V8缓存中出现了3种不同类型的 each 缓存,V8为了能够复用生成的代码,接下来的每一次 each 调用,都会做相应的类型检查。
测试一段代码
运行环境:Chrome JavaScript | V8 8.9.255.20
① 测试几次发现,array1和array2耗时相近,几乎是一样的; ② 取消第三行的注释再运行,测试发现,array1的耗时明显要小于array2;
或者直接在这测试:jsben
这是为什么呢?V8内部是如何处理数组操作的?
数组中的元素类型
在JavaScript层面,开发者可以给数组的元素设置任意基础数据类型,eg:
arr = [1, 1.2, 'hello', {}, []]
,然而,在V8层面,它理解的元素类型却只有三种:举个例子,暂时忽略
PACKED_
这个前缀:由上例子可知,V8为每个数组分配一种元素类型
SMI_ELEMENTS | DOUBLE_ELEMENTS | ELEMENTS
,并且元素类型的转换只能从特定的(eg:SMI_ELEMENTS )到更一般的(eg:ELEMENTS)。再看一个例子:
这个例子里面,arr出现了个洞(没有被完满填充)我们称其为稀疏数组,用一个![image](https://user-images.githubusercontent.com/26505680/113302868-116e7980-9333-11eb-8e5a-7b00bb2ac4fd.png)
HOLEY
去标识其类型;PACKED
则表示这是个密集数组。 稀疏数组是不可逆的,一旦标记为有空,它就是永远有洞,即便它被填充了!稀疏数组不可转变为密集数组,反之却可以。如下图表明了各元素类型之间的转换关系:而数组的稀疏与否,有可能(数据量足够大的时候)会影响数组操作的性能。
数组的性能问题
避免创建稀疏数组
new Array
创建数组// good const arr = [0, 0, 0, 0, 0];
// better const arr = Array.from({length: 5}
length
赋值// bad arr.length = 5;
在第一次调用中,数组类型是
PACKED_ELEMENTS
,V8使用inline cache
(内嵌缓存)记住each
函数是被一个特定元素类型调用的,在下一次调用时,V8会先检查数组类型是否是PACKED_ELEMENTS
,如果是,则复用上次生成的代码,否则会做更多的事情,如重新生成相应的代码;第二次调用的时候,数组类型是
PACKED_DOUBLE_ELEMENTS
,V8检查到出现了与缓存中不一样的数组类型,此时,V8将each
函数中的数组标记为多态,接下来的每一次each
调用都需要进行PACKED_ELEMENTS
和PACKED_DOUBLE_ELEMENTS
两种类型的检查,这会造成一些性能消耗;第三次调用结束的时候,V8缓存中出现了3种不同类型的
each
缓存,V8为了能够复用生成的代码,接下来的每一次each
调用,都会做相应的类型检查。然而,内置方法(eg:
Array.prototype.forEach
)可以更有效地处理这种多态性,因此在性能敏感的情况下应用优先考虑使用它。