JavaScript代码从我们敲下的文本变成计算机能执行的指令,中间要经过一系列编译和处理步骤。虽然常被称为解释型语言,但像V8这样的现代引擎其实内置了复杂的编译器,用来提升运行效率。理解这套机制,有助于我们写出性能更好的程序。

一、词法分析:把字符变成令牌
编译器第一步是词法分析,也叫扫描。引擎会逐字符读取源码,按照语法规则把字符串拆分成一个个有意义的令牌(token),比如关键字、标识符、运算符。例如 let 会被识别为关键字,a 是标识符。
// 简单模拟词法拆分思路
function tokenize(code) {
let tokens = [];
let current = 0;
while (current < code.length) {
let char = code[current];
if (char === ' ') { current++; continue; }
if (char === '=') { tokens.push({ type: 'assign' }); current++; continue; }
if (/[a-z]/i.test(char)) {
let value = '';
while (/[a-z]/i.test(code[current])) {
value += code[current];
current++;
}
tokens.push({ type: 'name', value });
continue;
}
current++;
}
return tokens;
}
二、语法分析:生成抽象语法树
拿到令牌流后,语法分析器会检查语法是否正确,并把令牌组合成抽象语法树(AST)。AST是代码结构的树状表示,每个节点代表一个语法单元,比如变量声明、赋值表达式。
| 阶段 | 输入 | 输出 |
|---|---|---|
| 词法分析 | 源码字符串 | 令牌数组 |
| 语法分析 | 令牌数组 | 抽象语法树 |
AST节点示例
对于 let a = 1,AST大致包含变量声明节点,内部有标识符 a 和字面量 1。我们可以用工具查看结构:
// 使用acorn库解析得到AST(示意)
const acorn = require('acorn');
let ast = acorn.parse('let a = 1', { ecmaVersion: 2020 });
console.log(ast.type); // Program
console.log(ast.body[0].type); // VariableDeclaration
三、字节码与即时编译
引擎不会直接把AST丢给CPU。以V8为例,先由解释器把AST转成字节码,字节码轻量且跨平台。随后在代码频繁执行时,即时编译器(JIT)会把热点的字节码编译为机器码,并做内联、去冗余等优化。
注意:如果函数只执行一次,可能始终以字节码形式运行,不会进入优化编译。
简单执行流程
- 源码文本
- 词法分析得到令牌
- 语法分析得到AST
- 生成字节码
- JIT编译热点为机器码
四、为什么了解编译器有用
当你知道 eval() 会打断优化、知道隐藏类影响属性访问速度,就能避开性能陷阱。比如固定对象属性顺序,有助于V8生成更稳定的机器码。编译器原理不是后台黑盒,它直接关系前端体验。
// 不利于优化的写法
function makeObj() {
let o = {};
o.x = 1;
o.y = 2;
return o;
}
// 利于优化的写法:顺序一致
function makeObj2() {
return { x: 1, y: 2 };
}
总体看,JavaScript编译器的工作就是让动态语言尽量接近静态语言的执行效率。从令牌到AST,再到字节码与机器码,每一层都在平衡启动速度和运行速度。
JavaScript编译器抽象语法树修改时间:2026-07-26 20:24:30