V8
Google 开发的 JavaScript 引擎,负责解析、编译、执行和优化 JavaScript,并管理运行期内存与垃圾回收。
[!info] related notes
V8
这是什么
V8 是 Google 开发的 JavaScript 引擎。
它的核心职责是:
- 解析 JavaScript 源码
- 把代码转换成可执行形式
- 执行并优化热点代码
- 管理对象、函数、闭包等运行期内存
- 负责垃圾回收
如果只用一句话概括:
V8 是 JavaScript 代码真正被“跑起来”的执行核心。
适用平台
- Chrome 等基于 Chromium 的浏览器环境
- Node.js 运行时
- 其他嵌入式 JavaScript 宿主环境
V8 本身不是浏览器,也不是 Node.js。它是这些宿主环境内部的 JavaScript 执行引擎。
核心特点 / 优势 / 局限
核心特点
- 把 JavaScript 从源码逐步转成字节码与机器码
- 通过 Ignition 和 TurboFan 组合做执行与优化
- 通过运行期信息优化热点代码
- 管理堆内存、调用栈和垃圾回收
- 既能服务浏览器,也能服务 Node.js
优势
- 执行速度高,能对热点路径持续优化
- 对现代 JavaScript 特性支持成熟
- 自动做内存管理与垃圾回收
- 很适合作为浏览器和服务端运行时的执行核心
局限
- 引擎本身较重,启动和内存成本不算低
- JavaScript 的动态类型会增加优化难度
- GC 仍可能带来停顿和延迟抖动
- 它只负责执行 JavaScript,不负责事件循环、DOM、文件系统或 HTTP
它和 Node.js、Chrome 的关系
最容易混淆的一点是:V8 不等于 Node.js,也不等于 Chrome。
在 Node.js 中
可以先粗略理解成:
Node.js = V8 + libuv + C++ bindings + Node 标准库
其中:
- V8 负责执行 JavaScript
- libuv 负责事件循环、线程池和异步 I/O 协调
- Node API 暴露
fs、http、stream、crypto等服务端能力
所以 fs.readFile() 背后的文件读取不是 V8 做的,但回调函数里的 JavaScript 逻辑仍然是 V8 在执行。
在 Chrome 中
Chrome 是完整浏览器,除了 V8 之外还包括:
- 渲染引擎
- DOM / BOM
- 网络模块
- 图形与页面渲染
因此 document.querySelector() 这样的 DOM API 不是 V8 发明的;V8 只是负责执行调用这些 API 的 JavaScript 代码。
它大致怎样执行一段 JavaScript
可以把 V8 的执行链路先记成下面这条主线:
JavaScript 源码
↓
Parser
↓
AST
↓
Ignition 字节码
↓
执行字节码
↓
发现热点代码
↓
TurboFan 优化编译
↓
机器码
1. 解析源码
V8 先读入 JavaScript 源码,并检查语法结构。
这一步会形成 AST,也就是抽象语法树。AST 不是最终执行结果,而是“代码结构”的中间表示。
2. 生成字节码
V8 不会一开始就把所有代码都做重度优化,而是先通过 Ignition 生成字节码。
字节码可以理解成:
- 比 JavaScript 更底层
- 比机器码更容易生成
- 启动更快,适合先运行起来
3. 优化热点代码
如果某段函数、循环或属性访问被频繁执行,V8 会把它视为热点代码,并收集运行期信息:
- 调用次数
- 参数类型
- 对象结构是否稳定
- 某个属性访问是否总落在同一种对象形状上
满足条件后,TurboFan 会把它进一步优化成更快的机器码。
JIT、热点代码与反优化
V8 的一个关键特征是 JIT,也就是即时编译。
它的思路不是“先把全部代码重编译完再跑”,而是:
- 先尽快执行
- 再观察哪些代码最常跑
- 对这些热点路径做更激进的优化
什么是热点代码
热点代码就是被频繁执行、值得额外投入优化成本的代码。
例如一个高频调用的函数、一个大循环里的属性访问,往往就会成为热点。
为什么还会反优化
V8 的优化建立在“运行期观察到的模式比较稳定”这个前提上。
如果它之前发现某函数总是处理数字,于是按数字路径优化;后来你又把字符串传进去,原来的假设可能失效,这时就会触发反优化,退回到更通用但更慢的执行方式。
这也是为什么很多性能经验都强调:
- 变量类型尽量稳定
- 热路径不要频繁改对象结构
V8 常见优化直觉
Hidden Class
JavaScript 对象对外看起来很灵活,但 V8 为了加速属性访问,会在内部为结构相近的对象建立隐藏类。
如果对象的属性集合和添加顺序长期稳定,V8 更容易优化属性访问;如果对象总是动态增删字段、顺序也不稳定,优化效果会变差。
Inline Cache
当某个属性访问路径反复出现,比如多次读取 user.name,并且 user 的对象形状稳定,V8 会缓存这类访问模式,避免每次都做完整查找。
对性能更友好的写法
- 高频路径里尽量保持参数类型稳定
- 尽量让对象结构稳定
- 少在热路径里频繁
delete属性 - 少做不必要的大对象创建和销毁
这些建议不是“语法规则”,而是为了让 V8 更容易保留优化状态。
内存管理、调用栈与 GC
V8 不只是执行代码,也负责运行期内存管理。
调用栈
函数调用会进入调用栈。递归过深或无限递归时,常见报错就是:
Maximum call stack size exceeded
更基础的心智模型见 调用栈和执行上下文的关系。
堆内存
对象、数组、函数等引用类型通常与堆内存管理更相关。堆不是“对象一定都在堆里”这种面试口号,而是用来理解运行期分配与回收的一个重要视角。
垃圾回收
当对象不再可达时,V8 才有机会回收它。
这也是为什么内存泄漏通常不是“忘记 free”,而是:
- 全局缓存一直持有对象
- 定时器或监听器没清理
- 闭包长期持有大对象
更完整的 GC 与泄漏背景看 ECMAScript内存管理。
V8 和事件循环不是一回事
这是 Node.js 面试里非常高频的混淆点。
V8 负责:
- 解析 JavaScript
- 编译和执行 JavaScript
- 管理内存和 GC
但事件循环不是 V8 本身提供的。
- 在 Node.js 里,事件循环主要来自 Node.js 运行时架构 里的 libuv
- 在浏览器里,事件循环由浏览器宿主环境实现
所以 setTimeout、DOM、fetch、文件系统这些能力,都不是 ECMAScript 语言本体或 V8 单独提供的。
常见用途
- 作为 Chrome 的 JavaScript 执行引擎
- 作为 Node.js 的 JavaScript 执行核心
- 为其他宿主环境提供可嵌入的 JavaScript 运行能力
- 支撑现代前端工具链和服务端 JavaScript 生态
相关链接 / 官方入口
- Node 侧关系:nodejs, Node.js 运行时架构
- Node 背景:Node.js 的起源与设计目标
- JavaScript 宿主边界:Node.js 下的 JavaScript
- 内存管理:ECMAScript内存管理
- 官方:https://v8.dev/