JavaScript 数组的快速模式和字典模式
在 V8 这类 JS 引擎里,数组有两种主要的内部存储方式:
- 快速模式(Fast Elements)
- 字典模式(Dictionary Elements,也叫 slow elements)
理解这两种模式,有助于写出性能更好的数组操作代码。
1. 快速模式(Fast Elements)
定义:索引从0到length-1且无空洞 或 预分配数组小于100000,无论有无空洞
快速模式可以类比「连续内存的动态数组」:
- 数组元素紧凑地存放在一块连续区域
- 通过索引访问时可以很快地算出偏移
- 引擎可以对这类访问生成非常高效的机器码
典型场景:
js
const arr = []
for (let i = 0; i < 100000; i++) {
arr.push(i)
}特点:
- 索引是从 0 开始的连续整数
- 不存在「很远的最大索引」
- 元素类型尽量稳定(比如大多都是 number)
只要数组大致满足「稠密 + 索引规则」,就会保持在快速模式。
2. 字典模式(Dictionary Elements)
定义:预分配数组大于等于100000,且数组有空洞
当数组变得「稀疏」或者结构很奇怪时,引擎会把数组退化为字典模式:
- 底层更像「索引 → 值」的哈希表
- 可以容忍非常大的索引、很多空洞
- 访问一个元素就需要查询字典,成本比快速模式高
例如:
js
const arr = []
arr[1000000] = 1在这种情况下:
- 前面几乎没有元素
- 直接写了一个很大的索引
- 为了不浪费大量空洞空间,引擎更倾向于用字典模式存储
3. 什么操作会让数组退化为字典模式?
常见几类「坑」:
3.1 使用非常大的索引
js
const arr = []
arr[10] = 1
arr[1000000] = 2- 这里会产生大量空洞
- 为了节省空间,数组容易从快速模式退化到字典模式
3.2 做成非常稀疏的数组
js
const arr = new Array(1000000)
arr[0] = 1
arr[999999] = 2- 中间几乎全是空位
- 这类「稀疏数组」更适合字典模式来存储
3.3 大量使用 delete 删除元素
js
const arr = [1, 2, 3, 4]
delete arr[1]
delete arr[2]delete不会改变 length,只是把位置变成「空洞」- 空洞多了,数组也很容易被引擎降级为字典模式
如果只是想「清空值」,通常更推荐:
js
const arr = [1, 2, 3, 4]
arr[1] = null
arr[2] = null这样不会制造稀疏结构。
3.4 把数组当作普通对象使用
js
const arr = []
arr[0] = 1
arr.foo = 'bar'
arr.bar = 'baz'虽然 JS 语法允许给数组挂任意属性,但这会让数组对象的形状变复杂, 数组相关访问更难保持在「纯粹的快速模式」下。
如果只是想存一组值 + 一些额外信息,更推荐用对象包裹:
js
const data = {
list: [1, 2, 3],
foo: 'bar'
}4. 编码建议:如何尽量停留在快速模式?
可以总结为几条简单规则:
- 尽量用
push/pop/shift/unshift这类「正常数组操作」 - 避免直接写入特别大的索引,尤其是跨越很多空位
- 少用
delete arr[i],改用arr[i] = null或重新构造数组 - 不要把数组当作「键值对对象」来使用,尽量只用整数索引
- 一旦需要「键值对结构」,可以直接上
Map或普通对象
5. 应该记住什么?
如果只记一句话,可以是:
对于需要高性能的场景,让数组「稠密、连续、只用整数索引」, 这样它就能一直待在快速模式里。
这背后对应的,就是 V8 等引擎对数组采用的快速模式和字典模式的切换策略。