从源码到执行:引擎的完整旅程
很多开发者知道"V8 是 Chrome 的 JavaScript 引擎",但说不清楚它具体做了什么。更重要的是,理解 V8 的执行流程,直接影响如何写出性能更好的代码——为什么同样的逻辑,换一种写法就快了几倍?为什么某些操作会让代码突然变慢?这些问题的答案,都藏在 V8 的执行…
/**
* 我不知道的 V8
* 共 17 篇文章
*/
export const series = {
name : "我不知道的 V8" ,
count : 17 ,
guide : "我不知道的 V8 — 系列导读与阅读路径" ,
description : "《我不知道的 V8》系列导读、阅读路径与文章索引。" ,
};
文章数量: 17 篇
根据不同背景选择入口:
如果想系统学习: 按编号 01 → 17 顺序阅读
如果只关心性能优化: 重点看 01、03、05、06、07、09、10
如果在调试异步问题: 重点看 12、13、14、15
| # | 文章 | 核心内容 |
|---|---|---|
| 01 | 从源码到执行:引擎的完整旅程 | 解析→字节码→JIT 完整链路,去优化机制 |
| 02 | 函数是如何变得”可调用”的 | JSFunction 内部结构,[[Call]] 机制,调用栈 |
| # | 文章 | 核心内容 |
|---|---|---|
| 03 | 为什么字典是”非线性”数据结构 | 线性 vs 非线性,快属性 vs 慢属性,哈希表原理 |
| 04 | 隐藏类、快属性与慢属性:对象性能的核心 | 隐藏类演变,Transitions,属性添加顺序的影响 |
| 05 | 为什么不建议使用 delete | delete 的内部步骤,隐藏类分裂,替代方案 |
| 06 | prototype 和 proto 的本质区别 | JSFunction vs JSObject,myNew 实现,原型链查找 |
| 07 | 函数内联:TurboFan 最重要的优化手段 | 内联触发条件,调用栈帧结构,去优化 |
| 08 | 字典模式下的非线性优化与限制 | NameDictionary 实现细节,哈希冲突,IC 对字典模式的支持 |
| # | 文章 | 核心内容 |
|---|---|---|
| 09 | 词法作用域与执行上下文:静态与动态的协作 | LexicalEnvironment,ScopeInfo,闭包与 Context 分配 |
| 10 | 1 + “2” 为什么等于 “12”:类型转换的底层逻辑 | ECMAScript 加法规范,ToPrimitive,字节码实现 |
| 11 | 惰性解析:V8 如何用”按需编译”提升启动速度 | PreParser,惰性策略,上下文分配 vs 栈分配 |
| # | 文章 | 核心内容 |
|---|---|---|
| 12 | 内联缓存:属性访问如何变成 O(1) 直接读 | IC 状态(单态/多态/超多态),IC 与 TurboFan 协作 |
| # | 文章 | 核心内容 |
|---|---|---|
| 13 | 宏任务的调度逻辑:事件循环的主干 | FIFO 宏任务队列,与渲染的关系,长任务分批 |
| 14 | 微任务:执行时机、优先级与底层实现 | MicrotaskQueue,PerformMicrotaskCheckpoint,queueMicrotask 生产用法 |
| 15 | MutationObserver:以微任务驱动的 DOM 监听 | 异步批量回调,MutationRecord,DocumentFragment 优化 |
| 16 | async/await:状态机、伪协程与微任务调度 | 状态机转换,await 的”暂停”本质,并行优化 |
| # | 文章 | 核心内容 |
|---|---|---|
| 17 | 垃圾回收:Scavenge、Mark-Sweep-Compact 与增量优化 | 新生代/老生代,复制算法,增量标记,并行 GC |
很多开发者知道"V8 是 Chrome 的 JavaScript 引擎",但说不清楚它具体做了什么。更重要的是,理解 V8 的执行流程,直接影响如何写出性能更好的代码——为什么同样的逻辑,换一种写法就快了几倍?为什么某些操作会让代码突然变慢?这些问题的答案,都藏在 V8 的执行…
在 JavaScript 里,函数既是对象(typeof fn === 'function',但 fn instanceof Object === true),又是可执行的代码块。这个"双重身份"不是语言层面的比喻,而是 V8 内部有具体实现的机制。一个普通对象写了 foo()…
"字典是非线性数据结构"这句话在很多前端技术文章里出现,但很少有人解释清楚:这里的"非线性"是什么意思,和"线性"相比到底差在哪,以及 V8 为什么要在对象属性存储上区分线性和非线性。
一个常见的认知是:JavaScript 是动态语言,对象可以随意增删属性,这天然就是慢的。但 V8 不接受这个结论。它在内部引入了隐藏类(Hidden Class)机制,让动态对象的属性访问接近静态语言的速度。理解这套机制,是理解 V8 对象优化的起点。
"delete 会破坏 V8 优化"——这个建议在前端社区流传很广,但通常没有解释背后的原因。知道"不要用"和知道"为什么不要用"是两回事。后者能帮你在真正需要用 delete 的场景里,做出更准确的判断。
prototype 和 proto 的混淆是 JavaScript 原型系统里最常见的误解之一。很多人知道"它们都和原型有关",但说不清楚谁属于谁、谁在什么时候起作用。这两个属性在 V8 内部分属不同的数据结构,服务于不同的目的——厘清这一点,原型链就从"需要记忆的规则"变成了…
函数调用在 JavaScript 里无处不在,但"调用一个函数"在 V8 内部并不简单。每次调用都要创建执行上下文、分配栈帧、传递参数、管理返回地址——这些开销在热点代码中积累,会变成性能瓶颈。TurboFan 用函数内联(Function Inlining)来消除这个开销。这…
前面的文章解释了 V8 为什么会切换到字典模式(Dictionary Mode)——对象结构变得不稳定,维持隐藏类的成本超过了收益。但字典模式并不意味着"放弃优化"。V8 在哈希表层面做了一系列针对性的优化,同时,字典模式也有硬性的性能天花板。理解这两面,才能在实际开发中做出准…
"作用域"和"执行上下文"这两个概念,很多 JavaScript 开发者混用。它们不是同一个东西,但联系很紧密。更少有人知道的是,这两个概念在 V8 内部有具体的数据结构对应——理解这些结构,才能真正解释闭包的行为,以及变量查找为什么是这个顺序。
1 + "2" 的结果是 "12" 而不是 3——大多数开发者知道这个结果,背后的理由通常是"JavaScript 会把数字转成字符串"。但这个说法不够精确。为什么是数字转成字符串,而不是字符串转成数字?+ 运算符究竟在什么时候做字符串拼接,什么时候做数字加法?ECMAScri…
加载一个大型 JavaScript 文件时,V8 并不会立即把里面所有函数都完整解析和编译。如果这样做,首次执行 的时间会随着代码量线性增长。V8 的实际策略是惰性解析(Lazy Parsing)——先快速扫描,推迟函数体的完整解析,等到函数真正被调用时再处理。这套机制和闭…
obj.x 看起来很简单,但 JavaScript 是动态类型语言,V8 不知道 obj 的 x 在哪——每次都去查?那性能没法看。V8 的解法是内联缓存(Inline Cache,IC):第一次访问时记录 "这种形状的对象,x 在 offset 0",后续直接复用这个结论,无…
setTimeout(fn, 0) 的 fn 为什么不是"立即"执行,而是"稍后"执行?为什么两个延时相同的 setTimeout 之间还有顺序关系?这些问题的答案在 V8 的事件循环设计里,宏任务(Macrotasks)是这套设计的核心单元。
"Promise 的 then 回调比 setTimeout 先执行"——大多数 JavaScript 开发者知道这个结论,但很少有人能说清楚为什么。这不是约定俗成的规则,而是 V8 内部有具体的执行机制在支撑:MicrotaskQueue 数据结构 + PerformMicr…
在 MutationObserver 出现之前,DOM 变化监听依赖 Mutation Events(如 DOMNodeInserted、DOMAttrModified)。这套 API 存在根本性的性能问题——每次 DOM 变化都同步触发事件,阻塞主线程,大量操作时性能灾难。M…
await 让函数"暂停",等 Promise 完成后"继续"——这个描述没错,但暂停是什么意思?主线程真的停下来等了吗?V8 用什么机制实现了"看似同步、实为异步"的执行模式?答案在 V8 把 async 函数转换成的状态机里,以及await 背后的微任务调度机制里。
JavaScript 的内存管理是自动的——代码里没有 malloc 和 free。但"自动"不意味着"没有代价"。V8 的垃圾回收(GC)在清理内存时会占用主线程,产生停顿。一次严重的 GC 停顿可以让页面卡顿 100ms 以上。理解 V8 的 GC 策略,不只是知识储备,直…