先抛出一个问题,下面两端代码哪个的效率更高?
const arr = [];
for(let i = 0; i < 10000000; i++) {
arr[i] = 1;
}
const arr = [];
arr[10000000 - 1] = 1;
for(let i = 0; i < 10000000; i++) {
arr[i] = 1;
}
答案是第一段代码的效率更高,先来看执行的耗时,运行下面的代码:
console.time('a')
const arr1 = [];
for (let i = 0; i < 10000000; i++) {
arr1[i] = 1;
}
console.timeEnd('a')
console.time('b')
const arr = [];
arr[10000000 - 1] = 1;
for (let i = 0; i < 10000000; i++) {
arr[i] = 1;
}
console.timeEnd('b')
控制台的输出如下:
可以很明显的看到执行耗时的差别,第一段代码的效率远高于第二段代码。
那么,这是为什么呢?
其实是 V8 底层对数组有两种处理方式,一种是 “快速模式”(Fast Elements),另一种是 “字典模式”(Dictionary Elements),那么这两种模式分别会在什么情况下触发,以及都有什么区别呢?
特点:快速模式是针对紧凑型数组(dense array)的优化,这种数组的特点是索引是连续的,没有“空洞”(即没有未定义的索引)。
内部处理:在快速模式下,V8 使用类似于 C++ 数组的结构来存储元素。这意味着它为数组元素分配了连续的内存空间,提高了元素访问的速度。
适用情况:当你创建的数组是紧凑的,且主要进行的是对数组元素的遍历和访问操作时,V8 会使用这种模式。
特点:当数组变得稀疏(sparse),即数组中有大量的空洞,或者数组索引非常大时,V8 会将数组转换为字典模式。
内部处理:在字典模式下,数组被处理为一个哈希表,而不是连续的内存块。这种方式在处理大量空洞或非常大的索引时更加内存高效。
适用情况:对于具有非常高索引值或大量未定义元素的数组,或者频繁更改其大小的数组,V8 会采用这种模式。
尽量使用快速模式,可以通过以下方式来处理长度过长的数组:
PS:绝大多数情况下都不太需要关注,除非这对性能的造成了很明显的影响或需要对性能进行重度优化的情况下,因为 V8 本身已经提供了很优秀的性能。
参考:https://itnext.io/v8-deep-dives-understanding-array-internals-5b17d7a28ecc
作者整理了一份长达 9w+ 字数,字节、阿里、百度大佬看了都称赞的前端文档,并且还在持续更新,包含:
可以添加作者 Q: 1437517225 备注领取,文档可以参与评论,以及给作者留言想要的问题,欢迎大家参与共建。