Skip to content

JavaScript 数组的快速模式和字典模式

在 V8 这类 JS 引擎里,数组有两种主要的内部存储方式:

  • 快速模式(Fast Elements)
  • 字典模式(Dictionary Elements,也叫 slow elements)

理解这两种模式,有助于写出性能更好的数组操作代码。

1. 快速模式(Fast Elements)

定义:索引从0length-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 等引擎对数组采用的快速模式和字典模式的切换策略。