MENU
- 一、四种结构的"真实身份"
- [二、为什么 Object 在大量 key 下会变慢?](#二、为什么 Object 在大量 key 下会变慢?)
- [三、为什么 Map 永远稳定快?](#三、为什么 Map 永远稳定快?)
- [四、为什么 Set 遍历反而比 Array 慢?](#四、为什么 Set 遍历反而比 Array 慢?)
- 五、真正的内存结构示意
- 六、为什么很多人代码慢却不知道原因?
- 七、终极理解(非常关键)
- 八、一句话升维总结
- [九、为什么 for (let i=0; i<arr.length; i++) 仍然是 JS 里最快的循环,没有之一](#九、为什么 for (let i=0; i<arr.length; i++) 仍然是 JS 里最快的循环,没有之一)
-
- 1、先给结论(很重要)
- [2、V8 是怎么执行 JS 循环的?](#2、V8 是怎么执行 JS 循环的?)
- [3、为什么 forEach 更慢?](#3、为什么 forEach 更慢?)
- [4、为什么 for...of 也不如 for?](#4、为什么 for...of 也不如 for?)
- 5、关键对比(非常重要)
- 6、真正的性能核心:不是语法,是"可预测性"
- [7、隐藏关键点:数组在 V8 里的真实形态](#7、隐藏关键点:数组在 V8 里的真实形态)
- [8、为什么 for 是"终极循环形态"?](#8、为什么 for 是“终极循环形态”?)
- 9、一句话终极本质
- [十、CPU + 编译器级别](#十、CPU + 编译器级别)
一、四种结构的"真实身份"
结构 你以为 引擎里其实是 核心用途 Array 列表 连续内存 + 索引 顺序访问 Object 键值对 字符串键哈希表(有退化路径) 配置/字典 Map 键值对 真正的通用哈希表 高频查找 Set 集合 只有 key 的哈希表 极快存在判断
二、为什么 Object 在大量 key 下会变慢?
很多人不知道:Object 不是一直都是哈希表。
在 V8 里,Object 有两种形态:
1、少量固定 key 时 ------ Hidden Class(快)
像这样:const o = { a: 1, b: 2 };
引擎会创建隐藏类(Hidden Class),属性访问变成:类似 C 结构体的偏移量访问(极快)
2、key 变多 / 动态增删时 ------ 退化为字典模式(慢)
for (let i = 0; i < 100000; i++) { o['k' + i] = i; }
此时:Object 会退化为 Dictionary Mode(哈希 + 链表),性能明显下降,内存也暴涨。
三、为什么 Map 永远稳定快?
const m = new Map();
Map 从设计开始就是:纯哈希表结构,没有 Hidden Class、没有退化路径
特性:
1、任意类型 key
2、不会退化
3、专为高频查找设计
4、内存布局为桶 + 链/开放寻址
所以当 key 很多时:Object <<<< Map差距非常明显。
四、为什么 Set 遍历反而比 Array 慢?
很多人踩这个坑:
for (const x of set) {}比for (const x of arr) {}慢很多。
原因:
Array Set 连续内存 哈希桶跳跃 CPU cache 友好 cache miss 严重 指针少 指针多 Set 每次遍历是:桶 → 指针 → 元素 → 下一个桶 → 指针...
这是CPU 最讨厌的访问模式。
所以:Set 快在查找,不快在遍历。
五、真正的内存结构示意
Array(极度友好)
[1][2][3][4][5]
一整块连续内存。
Set / Map(典型哈希桶)
javascriptbucket[0] → node → node bucket[1] bucket[2] → node bucket[3] → node → node → node大量指针跳转。
六、为什么很多人代码慢却不知道原因?
典型误用
javascriptconst set = new Set(bigList); for (const x of set) { ... } // ×等于用"查找结构"干"遍历结构"的活。
正确模式
javascriptconst set = new Set(bigList); for (const x of otherBigList) { if (set.has(x)) { ... } // √ }
七、终极理解(非常关键)
需求 正确结构 频繁遍历 Array 频繁存在判断 Set 大量 key 查值 Map 少量固定字段 Object 这不是语法选择。
是数据结构选择。
八、一句话升维总结
1、Array 是内存结构
2、Set / Map 是索引结构
3、Object 是语法糖结构(会退化)
九、为什么 for (let i=0; i<arr.length; i++) 仍然是 JS 里最快的循环,没有之一
1、先给结论(很重要)
在 V8 里:经典 for 循环之所以最快,本质是最容易被 JIT 编译成"纯机器码循环"
2、V8 是怎么执行 JS 循环的?
JS 代码不会直接执行,而是:
1、解释执行(Ignition)
2、热点代码 JIT 编译(TurboFan)
3、生成优化机器码
for 循环为什么容易被优化?
看这个:
javascriptfor (let i = 0; i < arr.length; i++) { sum += arr[i]; }V8 可以"确定"几件事:
1、i 是纯数字递增
没有类型变化
2、arr 是连续结构(大概率)
可当成 连续内存数组
3、length 是稳定值
可以 hoist(提取到循环外)
优化后变成类似:
javascriptfor (int i = 0; i < len; i++) { sum += memory_base[i]; }这已经接近 C 代码了
3、为什么 forEach 更慢?
javascriptarr.forEach(x => sum += x);问题
1、回调函数调用成本
每次循环:函数调用栈 + 参数绑定 + 作用域处理
2、无法 inline(内联优化困难)
JIT 很难把x => sum += x完全折叠进循环里
3、控制流不可预测
return / break / throw 语义复杂
4、为什么 for...of 也不如 for?
javascriptfor (const x of arr)问题
1、需要 iterator 协议
底层是arr[Symbol.iterator]()
2、每次 next() 调用
iterator.next()多一层抽象
3、无法完全消除边界检查
5、关键对比(非常重要)
写法 V8 优化等级 真实执行 for (i++) ⭐⭐⭐⭐⭐ 机器码循环 while ⭐⭐⭐⭐ 接近机器码 forEach ⭐⭐ 函数调用循环 for...of ⭐⭐ iterator循环
6、真正的性能核心:不是语法,是"可预测性"
V8 最喜欢的代码特征
类型稳定 →i = number
结构稳定 →arr = packed array
无抽象层 → 没有 callback / iterator
可线性访问 → 连续内存
7、隐藏关键点:数组在 V8 里的真实形态
数组不是"数组",而是:Elements Kind(元素类型系统)
类型 名字 性能 SMI_ELEMENTS 小整数数组 最快 DOUBLE_ELEMENTS 浮点数组 快 OBJECT_ELEMENTS 混合类型 慢 一旦这样做:
arr.push("string");
数组直接退化:SMI → OBJECT_ELEMENTS
性能下降一个档次
8、为什么 for 是"终极循环形态"?
1、没有函数调
2、没有 iterator
3、没有 closure 捕获
4、没有协议层
5、可以完全编译成 CPU 指令循环
9、一句话终极本质
for 循环之所以最快,不是因为语法简单
而是因为它最接近 CPU 的真实执行模型