浏览器原理 · 51/51
1. addEventListener 第三个参数 2. addEventListener 与 attachEvent 区别 3. 浏览器兼容性测试与内核 4. 浏览器兼容性问题 5. 浏览器内核与引擎 6. 浏览器图层创建条件 7. 浏览器多进程架构 8. 浏览器渲染机制 9. 浏览器存储方案 10. 浏览器版本检测方法 11. children 与 childNodes 区别 12. 常见浏览器兼容性问题 13. Chrome 页面进程数量 14. 坐标系统对比 15. 多标签页通讯方案 16. 删除 Cookie 17. 自定义事件 18. DOM 事件处理方式演进 19. 元素尺寸属性对比 20. DOM 节点操作 21. DOM 事件机制 22. addEventListener 与 attachEvent 的区别 23. 获取页面所有复选框 24. HTMLCollection 与 NodeList 区别 25. Hybrid 应用开发 26. 强缓存命中机制 27. 浏览器缓存机制 28. 页面编码与资源编码不一致处理 29. jQuery 事件绑定方法对比 30. Input 点击触发的事件顺序 31. JavaScript 浏览器兼容性问题 32. jQuery 多事件绑定实现 33. JSBridge 原理 34. 链接点击后 Hover 失效解决方案 35. 减少重绘和回流的性能优化 36. 移动端 300ms 点击延迟问题 37. 移动端视口配置 38. 移动端点击穿透问题解决 39. 移动端兼容性问题 40. JSBridge 原理与实现 41. 移动端 1px 像素问题解决方案 42. 浏览器渲染流程 43. 页面加载完成事件对比 44. Offset、Scroll、Client 属性对比 45. 同源策略与跨域解决方案 46. Script 标签位置对页面加载的影响 47. Service Worker 与 PWA 48. 存储方案对比:Cookie、Storage、IndexedDB 49. 强缓存默认时间 50. URL 到页面显示的完整过程 51. V8 引擎 JavaScript 执行过程

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