V8 引擎 JavaScript 执行过程
V8 执行 JS 的三个阶段:Parse、Ignition、TurboFan
问题
V8 引擎是如何执行 JavaScript 代码的?Parse、Ignition、TurboFan 分别做什么?
解答
执行流程概览
源代码 → Parser → AST → Ignition → 字节码 → TurboFan → 机器码
↑ ↓
←←← 去优化(Deopt)←←←←
1. Parser(解析器)
将 JavaScript 源代码转换为 AST(抽象语法树)。
// 源代码
function add(a, b) {
return a + b;
}
// 解析后的 AST 结构(简化)
{
type: "FunctionDeclaration",
id: { type: "Identifier", name: "add" },
params: [
{ type: "Identifier", name: "a" },
{ type: "Identifier", name: "b" }
],
body: {
type: "BlockStatement",
body: [{
type: "ReturnStatement",
argument: {
type: "BinaryExpression",
operator: "+",
left: { type: "Identifier", name: "a" },
right: { type: "Identifier", name: "b" }
}
}]
}
}
V8 使用两种解析策略:
// 预解析(Pre-parsing):只检查语法,不生成 AST
// 用于暂时不执行的函数,节省内存
function outer() {
// inner 函数先被预解析
function inner() {
// 大量代码...
}
// 调用时才完整解析
inner();
}
2. Ignition(解释器)
将 AST 转换为字节码并执行,同时收集类型反馈信息。
// 查看 V8 生成的字节码
// 运行:node --print-bytecode add.js
function add(a, b) {
return a + b;
}
add(1, 2);
// 输出的字节码(简化)
/*
[generated bytecode for function: add]
Parameter count 3
Register count 0
0 : Ldar a1 // 加载参数 a 到累加器
2 : Add a0, [0] // 累加器 + 参数 b
5 : Return // 返回结果
*/
Ignition 在执行时收集类型信息:
function add(a, b) {
return a + b;
}
// Ignition 记录:add 函数的参数都是整数
add(1, 2);
add(3, 4);
add(5, 6);
3. TurboFan(优化编译器)
当函数被多次调用(成为热点代码),TurboFan 根据类型反馈生成优化的机器码。
// 查看优化信息
// 运行:node --trace-opt add.js
function add(a, b) {
return a + b;
}
// 多次调用,触发优化
for (let i = 0; i < 10000; i++) {
add(i, i + 1);
}
// 输出:[marking add for optimization]
// 输出:[compiling method add using TurboFan]
去优化(Deoptimization)
当类型假设被打破,V8 会回退到字节码执行:
// 运行:node --trace-deopt deopt.js
function add(a, b) {
return a + b;
}
// 用整数调用,TurboFan 优化为整数加法
for (let i = 0; i < 10000; i++) {
add(i, i + 1);
}
// 传入字符串,类型假设被打破
add("hello", "world"); // 触发去优化
// 输出:[deoptimizing: add, reason: not a Number]
优化建议
// ❌ 避免:类型不稳定
function process(input) {
return input.x + 1;
}
process({ x: 1 });
process({ x: 2, y: 3 }); // 对象形状不同,影响优化
// ✅ 推荐:保持类型稳定
function process(input) {
return input.x + 1;
}
process({ x: 1 });
process({ x: 2 }); // 对象形状相同
// ❌ 避免:参数类型变化
function add(a, b) {
return a + b;
}
add(1, 2);
add("a", "b"); // 触发去优化
// ✅ 推荐:参数类型一致
function addNumbers(a, b) {
return a + b;
}
function concatStrings(a, b) {
return a + b;
}
关键点
- Parser 将源码转为 AST,使用预解析延迟处理未执行的函数
- Ignition 是解释器,将 AST 编译为字节码执行,同时收集类型反馈
- TurboFan 是优化编译器,将热点代码编译为高效机器码
- 去优化:当运行时类型与优化假设不符,回退到字节码执行
- 编写优化友好的代码:保持变量类型稳定、对象形状一致
目录