文章首发:github.com/qiudongwei/…git
运行环境:Chrome JavaScript | V8 8.9.255.20github
let array1 = [1];
let array2 = [1];
// array2[2]=2; // 超出数组长度
console.time('array1');
for (let i = 0; i < 10000000; i++) {
array1.push(i);
}
console.timeEnd('array1');
console.time('array2');
for (let j = 0; j < 10000000; j++) {
array2.push(j);
}
console.timeEnd('array2');
复制代码
① 测试几回发现,array1和array2耗时相近,几乎是同样的; ② 取消第三行的注释再运行,测试发现,array1的耗时明显要小于array2;数组
或者直接在这测试:jsben缓存
这是为何呢?V8内部是如何处理数组操做的?性能优化
在JavaScript层面,开发者能够给数组的元素设置任意基础数据类型,eg:arr = [1, 1.2, 'hello', {}, []]
,然而,在V8层面,它理解的元素类型却只有三种:markdown
举个例子,暂时忽略PACKED_
这个前缀:app
const arr = [1, 2, 3]; // 元素类型: PACKED_SMI_ELEMENTS
arr.push(1.54); // 元素类型变为: PACKED_DOUBLE_ELEMENTS
arr.push('c'); // 元素类型变为: PACKED_ELEMENTS
复制代码
由上例子可知,V8为每一个数组分配一种元素类型 SMI_ELEMENTS | DOUBLE_ELEMENTS | ELEMENTS
,而且元素类型的转换只能从特定的(eg:SMI_ELEMENTS )到更通常的(eg:ELEMENTS)。函数
再看一个例子:oop
const arr = [1, 2, ,4]; // 元素类型HOLEY_SMI_ELEMENTS
arr.push(5); // 元素类型变为: HOLEY_DOUBLE_ELEMENTS
arr.push('a'); // 元素类型变为: HOLEY_ELEMENTS
复制代码
这个例子里面,arr出现了个洞(没有被完满填充)咱们称其为稀疏数组,用一个HOLEY
去标识其类型; PACKED
则表示这是个密集数组。 稀疏数组是不可逆的,一旦标记为有空,它就是永远有洞,即使它被填充了!稀疏数组不可转变为密集数组,反之却能够。以下图代表了各元素类型之间的转换关系: 性能
而数组的稀疏与否,有可能(数据量足够大的时候)会影响数组操做的性能。
new Array
建立数组// bad
const arr = new array(5);
// good
const arr = [0, 0, 0, 0, 0];
// better
const arr = Array.from({length: 5}
复制代码
const arr = [1, 2, 3];
// bad
log(arr[3]);
复制代码
length
赋值const arr = [1, 2, 3];
// bad
arr.length = 5;
复制代码
若是您的代码须要处理包含多种不一样元素类型的数组,则可能会比单个元素类型数组要慢,由于你的代码要对不一样类型的数组元素进行多态操做。看这个例子:
const each = (array, callback) => {
for (let index = 0; index < array.length; ++index) {
const item = array[index];
callback(item);
}
};
// 第一次调用
each(['a', 'b', 'c'], doSomething);
// 第二次调用
each([1.1, 2.2, 3.3], doSomething);
// 第三次调用
each([1, 2, 3], doSomething);
复制代码
在第一次调用中,数组类型是 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
)能够更有效地处理这种多态性,所以在性能敏感的状况下应用优先考虑使用它。
参考: