数组访问快慢取决于V8是否将其视为连续内存块(Packed)或带洞散列结构(Holey):Packed数组索引连续、无空洞,支持O(1)地址计算;Holey数组因存在未定义索引或delete操作而触发空洞检查及原型链查找,性能骤降。

数组访问快不快,关键不在代码写法,而在 V8 内部把它当成“连续块”还是“带洞的散列结构”。一旦掉进 Holey 模式,每次读取都可能触发原型链查找或空洞检查,性能直接打骨折。
什么是 Packed 和 Holey?
Packed(连续)数组:所有索引从 0 开始连续有值,比如 [1, 2, 3, 4] 或 new Array(1000).fill(0)。V8 把它存成一块紧凑内存,arr[i] 就是纯地址计算——基址 + i × 元素大小,一步到位。
Holey(孔洞)数组:中间缺了某些索引,比如 [1, , 3]、arr[100] = 5(前面全没赋值)、或执行过 delete arr[2]。V8 必须标记这些“空位”,访问时先查是不是 the_hole,再决定是否往上翻原型链。
一个 delete 就能让你的数组变慢 5 倍
delete arr[i] 是最隐蔽的性能雷。它不会移动元素,只是把那个位置设为 hole,而且这个 hole 永远不会自动填平。之后所有对 arr[i] 的访问都会走慢路径。
- 用
arr.splice(i, 1)替代delete,它会真正收缩数组并保持 packed 状态 - 想清空某项但保留长度?改用
arr[i] = undefined——这不会产生 hole,只是存个值 - 初始化稀疏数据时,别用
arr[999] = x,改用Array.from({length: 1000}, (_, i) => i === 999 ? x : undefined)并确保后续不删
怎么知道你的数组是不是 holey?
V8 不暴露内部形态,但你可以靠行为和工具判断:
- 控制台打印数组,如果看到
[1, empty, 3]或[1, , 3],基本就是 holey - 用 Chrome DevTools 的“Memory”面板拍堆快照,筛选
JSArray,看Elements字段是否含HOLEY - 性能敏感场景下,加一句
console.time(); for (let i = 0; i 对比 packed 数组,差距明显就值得查
连续 ≠ 物理内存绝对连续,但足够“友好”
严格说,V8 的 packed 数组也不是 C 那种固定长度连续块——它仍可动态扩容,底层是带元信息的连续堆区。但它满足两个关键缓存友好条件:
- 元素紧挨着存放,一个 64 字节缓存行能塞进 16 个 32 位整数
- 顺序访问时 CPU 预取器能准确抓到下一批数据,避免频繁主存等待
- 而 holey 或 dictionary 模式下,每个元素可能是堆中任意地址的指针,缓存行利用率暴跌,实测遍历速度常差 5–10 倍










