JavaScript 引擎工作原理:解析、AST、JIT 编译、Ignition/TurboFan、隐藏类、内联缓存、垃圾回收,是前端性能优化的底层根基。


一、开篇:代码到底是怎么跑起来的?

你写的 JS 代码:

function greet(name) {
  return "Hello, " + name + "!"
}
greet("World")

从文本到 CPU 执行,全靠 JavaScript 引擎。主流引擎:

  • Chrome / Node.js / Deno / Electron:V8
  • Firefox:SpiderMonkey
  • Safari / Bun:JavaScriptCore

理解引擎 = 写出更快的代码 + 搞定性能问题。


二、本文你将学到

  • JS 引擎是什么、做什么
  • V8 解析流程与 AST 抽象语法树
  • Ignition(解释器)+ TurboFan(编译器)协作
  • JIT 即时编译原理
  • 隐藏类(Hidden Class)与内联缓存(IC)
  • 垃圾回收(GC)机制
  • 写给引擎看的 “友好代码” 实践

三、什么是 JavaScript 引擎?

JS 引擎是一个把源码转换成机器码并执行的程序。所有引擎都遵循 ECMAScript 标准,所以代码在各环境行为基本一致。

主流引擎对照表:

浏览器 / 运行时 引擎
Chrome, Node.js, Deno, Electron V8
Firefox SpiderMonkey
Safari, Bun JavaScriptCore
Edge(2020 年后) V8

四、V8 执行代码的全过程(工厂类比)

  1. 源码 → 原材料
  2. 解析(Parser) → 质量检查,拆分 Token
  3. AST(抽象语法树) → 施工蓝图
  4. Ignition(解释器) → 快速开工,生成字节码
  5. TurboFan(优化编译器) → 热点代码编译成极速机器码
  6. 反优化(Deoptimization) → 假设失败,退回字节码

五、阶段 1:解析 Parsing

两步:

  1. 分词(Tokenization)把代码拆成最小有意义单元:functionadd(a+} ...

  2. 语法分析(AST)生成抽象语法树,表示代码逻辑结构。你可以在 AST Explorer 在线查看。


六、阶段 2:Ignition 解释器

V8 5.9 后默认解释器。

  • 生成字节码(Bytecode)
  • 体积小、启动快
  • 一边执行一边收集分析数据(类型、调用次数)

字节码示例:

Ldar a1
Add a2
Return

七、阶段 3:TurboFan 优化编译器

当函数调用频繁(热点代码),Ignition 会通知 TurboFan 优化。基于收集的类型信息生成高效机器码

优化依据:

  • 函数总是传数字
  • 对象结构稳定
  • 分支总是走某条路

优化后速度可提升 几十倍


八、阶段 4:反优化 Deoptimization

如果 TurboFan 的假设被打破

  • 突然传入字符串
  • 对象结构突变
  • 类型乱变

V8 会:

  • 丢掉优化机器码
  • 退回 Ignition 字节码
  • 稍后重新分析优化

九、什么是 JIT 编译?

JS 不是解释型,也不是预编译型,而是 JIT 即时编译

三种执行模式对比:

  • 解释型:启动快,跑很慢
  • 预编译(AOT):启动慢,跑极快
  • JIT(V8):启动快 + 跑很快 = 兼顾

JIT 特别适合 JS 这种动态类型语言


十、隐藏类 Hidden Class(Map/Shape)

V8 给每个对象分配一个隐藏类,描述对象结构:

  • 有哪些属性
  • 属性顺序
  • 内存偏移量

相同结构的对象共享同一个隐藏类 → 极快属性访问

重点规则:属性添加顺序不同 → 不同隐藏类 → 无法共享优化

const a = { x:1, y:2 }
const b = { y:2, x:1 } // 不同隐藏类!

十一、内联缓存 Inline Caching(IC)

引擎记住:“对于这个隐藏类,属性 x 在内存偏移 0 处”

下次直接读内存,不用查表。速度从 O (n) → O (1)。

IC 三种状态:

  1. 单态(Monomorphic):最快,同一种结构
  2. 多态(Polymorphic):较快,少量结构
  3. 超态(Megamorphic):最慢,无数结构

保持单态 = 性能提升关键


十二、垃圾回收 GC(Orinoco)

JS 自动内存管理,V8 采用分代回收

分代假说

绝大多数对象 “朝生暮死”。

内存堆分为:

  • 新生代:存放短命对象,快速频繁回收(Scavenger)
  • 老生代:存放长寿对象,慢速但彻底回收(Mark-Compact)

GC 流程

  1. 标记:从根对象遍历,标记存活对象
  2. 清除:回收未标记对象
  3. 压缩:整理内存碎片

现代 V8 使用:

  • 并行 GC
  • 增量 GC
  • 并发 GC

尽量不阻塞主线程。


十三、写给引擎的友好代码(实战优化)

  1. 保持对象结构一致统一顺序初始化属性,用工厂 / Class。

  2. 不要乱改变量类型数字就一直数字,别变字符串 /null。

  3. 不要用 deletedelete 会破坏隐藏类,改用 undefinedMap

  4. 避免稀疏数组不要 arr[1000] = 1,会产生空洞。

  5. 保持函数单态传入结构相同的对象。

  6. 避开 eval /new Function彻底阻断优化。


十四、常见误区

  • ❌ “JS 是解释型语言”✅ 现代 JS 是 JIT 编译型

  • ❌ “代码越少越快”✅ 清晰、稳定、可预测的代码更快

  • ❌ “我要手动管理内存”✅ GC 自动管理,只需避免内存泄漏

  • ❌ “typeof null 是 bug”✅ 这是标准规定,历史遗留


十五、核心总结

  • V8 = 目前最主流 JS 引擎
  • 执行流程:源码 → 解析 → AST → 字节码 → 优化机器码
  • Ignition 快启动,TurboFan 热优化
  • 隐藏类 + 内联缓存 = 对象属性极速访问
  • 单态最快,超态最慢
  • 分代垃圾回收,高效自动管理内存
  • 代码稳定、结构统一 = 引擎跑得飞快

🧩 概念 汇总

1. 引擎与执行模型

  • JavaScript 引擎同类:JS 运行时、虚拟机、执行环境
  • V8 引擎同类:SpiderMonkey、JavaScriptCore、Hermes
  • JIT 即时编译同类:即时编译、运行时编译、混合执行
  • AOT 预编译同类:提前编译、静态编译
  • 解释执行同类:边解释边执行、无编译启动

2. 编译与中间表示

  • Parsing 解析同类:词法分析、语法分析、编译前端
  • Tokenization 分词同类:词法扫描、token 流
  • AST 抽象语法树同类:语法结构树、中间表示
  • Bytecode 字节码同类:中间码、跨平台指令集
  • 机器码同类:原生指令、CPU 指令

3. V8 双引擎架构

  • Ignition 解释器同类:基线执行、快速执行、字节码解释
  • TurboFan 优化编译器同类:优化后端、机器码生成、热点编译
  • Deoptimization 反优化同类:回退执行、假设失效、重新编译

4. 对象优化机制

  • Hidden Class 隐藏类同类:Map、Shape、对象结构描述符
  • Transition Chain 转换链同类:结构渐变、隐藏类迁移
  • Inline Caching 内联缓存同类:IC、属性偏移缓存、快速访问
  • Monomorphic 单态同类:单一结构、最快缓存
  • Polymorphic 多态同类:少量结构、较快缓存
  • Megamorphic 超态同类:无数结构、无优化、最慢

5. 内存与垃圾回收

  • Generational GC 分代回收同类:分代假说、年轻代 / 老生代
  • Minor GC 小 GC同类:Scavenger、新生代回收、快速 GC
  • Major GC 大 GC同类:Mark-Compact、全量 GC
  • Concurrent GC 并发 GC同类:后台 GC、不阻塞主线程
  • Memory Leak 内存泄漏同类:意外引用、无法 GC、闭包泄漏

 

更多推荐