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 是优化编译器,将热点代码编译为高效机器码
  • 去优化:当运行时类型与优化假设不符,回退到字节码执行
  • 编写优化友好的代码:保持变量类型稳定、对象形状一致