Skip to main content

JS 编译流程与 AST 语法树

为什么要了解 AST

要了解 JS 编译执行的原理,就得先了解 AST,目前前端常用的一些插件或者工具,例如编辑器的错误提示、代码格式化(ESLint、Prettier)、代码高亮、代码自动补全、代码压缩、Babel、CSS 预处理器等,都建立在 AST 的基础之上。

一、JS 编译执行流程

JS 执行的第一步是读取 JS 文件中的字符流,然后通过词法分析生成 Token,之后再通过语法分析(Parser)生成 AST(Abstract Syntax Tree),最后生成机器码执行。

整个解析过程分为以下两个步骤:

  • 词法分析:将代码分割成最小语法单元数组
  • 语法分析:在词法分析的基础上建立分析语法单元间的关系
什么是 JS Parser

JS Parser 是 JS 语法解析器,它可以将 JS 源码转成 AST,常见的 Parser 有 esprima、traceur、acorn、shift 等。

点击查看 Parser API 规范

二、词法分析和语法分析

1、词法分析

词法分析,也称之为扫描(scanner),就是调用 next() 方法,将字符串形式的代码转换为一个语法片段 Tokens 数组,同时过滤掉源程序中的注释和空白符(换行符、空格、制表符)等。

  • Token(令牌):一个不可分割的最小单元,例如 var 这三个字符,只能作为一个整体,语义上不能再被分解,是一个 Token。另外,每个标识符是一个 Token,每个操作符是一个 Token,每个标点符号也是一个 Token
  • Tokens(令牌流):一个由 Token 组成的数组,内部可以是数字、标签、标点符号、运算符等。
// 示例源码
a + b

// Tokens
[
{ type: { ... }, value: "a", start: 0, end: 1, loc: { ... } },
{ type: { ... }, value: "+", start: 2, end: 3, loc: { ... } },
{ type: { ... }, value: "b", start: 4, end: 5, loc: { ... } },
]

2、语法分析

语法分析阶段会把 Tokens 转换成抽象语法树 AST,同时验证语法,抛出语法错误。

三、什么是 AST 抽象语法树

AST(Abstract Syntax Tree)抽象语法树,是源代码语法结构的一种抽象表示,以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

举个例子:

function add(a, b) {
return a + b
}
FunctionDeclaration {
id: Identifier {
name:"add"
},
params: {
Identifier {
name:"a"
},
Identifier {
name:"b"
},
},
body: BlockStatement {
body: {
ReturnStatement {
argument: BinaryExpression {
left: Identifier {
name:"a"
},
operator:"+",
right: Identifier {
name:"b"
},
}
}
}
}
}

JS 代码可以直接通过 AST explorer 或 VSCode 的 babel-ast-explorer 插件查看其 AST 结构。

四、AST 语法树的应用

1、@babel/parser 转 AST

@babel/parser 基于 acornacorn-jsx,是 Babel 中的 JS 解析器,用于将 JS 代码转为 AST,安装如下:

yarn add @babel/parser --dev

使用:

import { parse } from '@babel/parser'

function compile(code) {
const ast = parse(code)
console.log(ast)
}

const code = `
function add(a, b) {
return a + b
}
`

compile(code)

输出:

2、@babel/generator 转回 JS

@babel/generator 用于将 AST 结构转回 JS 代码,安装如下:

yarn add @babel/generator --dev

使用:

import { parse } from '@babel/parser'
import generate from '@babel/generator'

function compile(code) {
const ast = parse(code)
const output = generate(ast)
console.log(output)
}

const code = `
function add(a, b) {
return a + b
}
`

compile(code)

输出:

注意

运行如果出现 process is not definedBuffer is not defined 等问题,可通过在项目 config 文件中进行配置来解决。

webpack 解决方法:

yarn add process buffer

webpack.config.js:

const webpack = require('webpack')

module.exports = {
// ...
plugins: [
// ...
new webpack.ProvidePlugin({
process: 'process/browser',
}),
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
}),
]
}

3、中间处理 AST 结构的过程

对代码的处理就是在 JS 转成 AST 后,对 AST 节点进行递归遍历并修改,最后将修改后的 AST 转回 JS 代码。

3-1、@babel/traverse 遍历 AST

@babel/traverse 用于对 AST 节点进行递归遍历,安装如下:

yarn add @babel/traverse --dev

通过 traverse 打印出每个节点:

import { parse } from '@babel/parser'
import generate from '@babel/generator'
import traverse from "@babel/traverse"

function compile(code) {
const ast = parse(code)
traverse(ast, {
enter(path) {
// 打印出每个节点
console.log(path)
},
});
const output = generate(ast)
return output
}

const code = `
function add(a, b) {
return a + b
}
`

compile(code)

输出:

traverse 遍历过程中可以对节点进行修改,例如通过 traversea + b 替换成 a * b

import { parse } from '@babel/parser'
import generate from '@babel/generator'
import traverse from "@babel/traverse"

function compile(code) {
const ast = parse(code)
traverse(ast, {
enter(path) {
if (path.isBinaryExpression({ operator: "+" })) {
path.node.operator = "*";
}
},
});
const output = generate(ast)
return output
}

const code = `
function add(a, b) {
return a + b
}
`

const newCode = compile(code)
console.log(newCode)

输出:

3-2、@babel/types 修改 AST

上面通过 traverse 对 AST 进行遍历并修改,而 @babel/types 是一个用于处理 AST 节点的 Lodash 式工具库,提供了构造、验证以及变换 AST 节点的方法,可更方便的对 AST 节点进行修改,安装如下:

yarn add @babel/types --dev

使用:

import { parse } from '@babel/parser'
import generate from '@babel/generator'
import traverse from "@babel/traverse"
import * as t from "@babel/types"

function compile(code) {
const ast = parse(code)
traverse(ast, {
enter(path) {
if (path.isBinaryExpression({ operator: "+" })) {
if (t.isBinaryExpression(path.node, { operator: "+" })) {
path.node.operator = "*";
}
},
});
const output = generate(ast)
return output
}

const code = `
function add(a, b) {
return a + b
}
`

const newCode = compile(code)
console.log(newCode)

点击查看更多方法

4、应用实例

举个例子,通过 AST “手术刀”,给每次调用 console.log(xx) 时在前面打印函数名,方便 log 打印调试时知道是哪个函数调用的:

import { parse } from '@babel/parser'
import generate from '@babel/generator'
import traverse from "@babel/traverse"
import * as t from "@babel/types"

const visitor = {
enter(path) {
const { callee } = path.node

const isConsoleLog =
t.isMemberExpression(callee) &&
callee.object.name === "console" &&
callee.property.name === "log"

if (isConsoleLog) {
const funcPath = path.findParent(p => {
return p.isFunctionDeclaration()
})
const funcName = funcPath.node.id.name
path.node.arguments.unshift(t.stringLiteral(funcName))
}
}
}

function compile(code) {
const ast = parse(code)
traverse(ast, visitor)
const output = generate(ast, {}, code)
return output
}

const code = `
function getData() {
console.log('data')
}
`

const newCode = compile(code)
console.log(newCode.code)

输出:

点击查看如何编写一个 Babel 插件

五、AST 节点说明汇总

1、Identifier

标识符,即写 JS 时自定义的名称,如变量名,函数名,属性名等。相应的接口如下:

interface Identifier <: Expression, Pattern {
type: "Identifier";
name: string;
}

2、Literal

字面量,这里不是指 []{},而是本身语义就代表一个值的字面量,如 1“hello”, true 这些,以及正则表达式如 /\d?/

interface Literal <: Expression {
type: "Literal";
value: string | boolean | null | number | RegExp; // 对应字面量的值
}

3、RegExpLiteral

针对正则字面量,用于解析正则表达式的内容,添加多一个 regex 字段,里面包括正则本身以及正则的 flags

interface RegExpLiteral <: Literal {
regex: {
pattern: string;
flags: string;
};
}

4、Programs

一般作为根节点,代表一棵完整的程序代码树。

interface Program <: Node {
type: "Program";
body: [ Statement ]; // 一个数组,包含多个 Statement(即语句)节点
}

5、Functions

函数声明或函数表达式节点。

interface Function <: Node {
id: Identifier | null; // 函数名
params: [ Pattern ]; // 是一个表示参数的数组
body: BlockStatement; // 块语句
}
注意

测试过程中不会找到 type: "Function" 节点,但可以找到 type: "FunctionDeclaration"type: "FunctionExpression",因为函数要么以声明语句出现,要么以函数表达式出现,都是节点类型的组合类型。

6、Statement

一个语句节点,是一种区分。

interface Statement <: Node { }

7、ExpressionStatement

表达式语句节点,a = a + 1a++ 中会有一个 expression 属性指向一个表达式节点对象。

interface ExpressionStatement <: Statement {
type: "ExpressionStatement";
expression: Expression;
}

8、BlockStatement

块语句节点。举个例子:

if (...) { // 这里是块语句的内容 } 块中可包含多个其他语句,所以有一个 body 属性,是一个数组,表示了块里面的多个语句。

interface BlockStatement <: Statement {
type: "BlockStatement";
body: [ Statement ];
}

9、EmptyStatement

一个空语句节点,没有执行任何有用的代码,例如一个单独的分号 ;

interface EmptyStatement <: Statement {
type: "EmptyStatement";
}

10、DebuggerStatement

debugger

interface DebuggerStatement <: Statement {
type: "DebuggerStatement";
}

11、WithStatement

with 语句节点。

interface WithStatement <: Statement {
type: "WithStatement";
object: Expression; // 表示 with 要使用的对象
body: Statement; // 对应 with 后边要执行的语句
}

12、控制流语句

12-1、ReturnStatement

返回语句节点。

interface ReturnStatement <: Statement {
type: "ReturnStatement";
argument: Expression | null; // 代表返回的内容
}

12-2、LabeledStatement

label 语句,举个例子:

loop: for(let i = 0; i < len; i++) {
// ...
for (let j = 0; j < min; j++) {
// ...
break loop;
}
}

这里的 loop 就是一个 label,可以在循环嵌套中使用 break loop 来指定跳出哪个循环。label 语句指的就是 loop: ...

interface LabeledStatement <: Statement {
type: "LabeledStatement";
label: Identifier; // label 的名称
body: Statement; // 指向对应的语句,通常是循环或 switch 语句
}

12-3、BreakStatement

break 语句节点。

interface BreakStatement <: Statement {
type: "BreakStatement";
label: Identifier | null; // 表示需要的 label 名称,当不需要 label 时为 null
}

12-4、ContinueStatement

continue 语句节点,和 break 类似。

interface ContinueStatement <: Statement {
type: "ContinueStatement";
label: Identifier | null;
}

13、条件语句

13-1、IfStatement

if 语句节点。

interface IfStatement <: Statement {
type: "IfStatement";
test: Expression; // 表示 if (...) 括号中的表达式
consequent: Statement; // 表示条件为 true 时的执行语句,通常是块语句
// alternate 表示 else 后跟随的语句节点,通常是块语句
// 也可以是个 if 语句节点,即 if (a) { //... } else if (b) { // ... }
// 也可以为 null
alternate: Statement | null;
}

13-2、SwitchStatement

switch 语句节点。

interface SwitchStatement <: Statement {
type: "SwitchStatement";
discriminant: Expression; // 表示 switch 语句后紧随的表达式,通常会是一个变量
cases: [ SwitchCase ]; // 一个 case 节点的数组,用来表示各个 case 语句
}

13-3、SwitchCase

switchcase 节点。

interface SwitchCase <: Node {
type: "SwitchCase";
test: Expression | null; // 表示 case 的判断表达式,为 null 时表示 default 这个 case 节点
consequent: [ Statement ]; // 表示 case 的执行语句
}

14、异常语句

14-1、ThrowStatement

throw 语句节点。

interface ThrowStatement <: Statement {
type: "ThrowStatement";
argument: Expression; // 表示 throw 后边紧跟的表达式
}

14-2、TryStatement

try 语句节点。

interface TryStatement <: Statement {
type: "TryStatement";
block: BlockStatement; // 表示 try 的执行语句,通常是一个块语句
handler: CatchClause | null; // 指 catch 节点
// finalizer 指 finally 语句节点,为 null 时必须是个块语句节点
finalizer: BlockStatement | null;
}

14-3、CatchClause

catch 节点。

interface CatchClause <: Node {
type: "CatchClause";
param: Pattern; // 表示 catch 后的参数
body: BlockStatement; // 表示 catch 后的执行语句,通常是个块语句
}

15、循环语句

15-1、WhileStatement

while 语句节点。

interface WhileStatement <: Statement {
type: "WhileStatement";
test: Expression; // 表示括号中的表达式
body: Statement; // 表示要循环执行的语句
}

15-2、DoWhileStatement

do/while 语句节点,和 while 语句类似。

interface DoWhileStatement <: Statement {
type: "DoWhileStatement";
body: Statement;
test: Expression;
}

15-3、ForStatement

for 循环语句节点。

init/test/update 三个属性都可以为 null,即 for(;;){}

interface ForStatement <: Statement {
type: "ForStatement";
init: VariableDeclaration | Expression | null; // 初始化值
test: Expression | null; // 循环判断条件
update: Expression | null; // 每次循环执行的变量更新语句
body: Statement; // 表示要循环执行的语句
}

15-4、ForInStatement

for/in 语句节点。

interface ForInStatement <: Statement {
type: "ForInStatement";
left: VariableDeclaration | Pattern; // in 关键词左侧语句
right: Expression; // in 关键词右侧语句
body: Statement; // 表示要循环执行的语句
}

16、声明语句

16-1、Declarations

声明语句节点,同样也是语句,只是一个类型的细化。

interface Declaration <: Statement { }

16-2、FunctionDeclaration

函数声明,和之前 Function 不同的是,id 不能为 null

interface FunctionDeclaration <: Function, Declaration {
type: "FunctionDeclaration";
id: Identifier;
}

16-3、VariableDeclaration

变量声明。

interface VariableDeclaration <: Declaration {
type: "VariableDeclaration";
declarations: [ VariableDeclarator ]; // 表示声明的多个描述,例如:let a = 1, b = 2;
kind: "var"; // 表示是什么类型的声明,因为 ES6 引入了 const/let
}

16-4、VariableDeclarator

变量声明的描述。

interface VariableDeclarator <: Node {
type: "VariableDeclarator";
id: Pattern; // 表示变量名称节点
init: Expression | null; // 表示初始值的表达式,可以为 null
}

17、表达式语句

17-1、Expressions

表达式节点。

interface Expression <: Node { }

17-2、ThisExpression

表示 this

interface ThisExpression <: Expression {
type: "ThisExpression";
}

17-3、ArrayExpression

数组表达式节点。

interface ArrayExpression <: Expression {
type: "ArrayExpression";
elements: [ Expression | null ]; // 一个数组,表示数组的多个元素,每一个元素都是一个表达式节点
}

17-4、ObjectExpression

对象表达式节点。

interface ObjectExpression <: Expression {
type: "ObjectExpression";
properties: [ Property ]; // 一个数组,表示对象的每一个键值对,每个元素都是一个属性节点
}

17-5、Property

对象表达式中的属性节点。

interface Property <: Node {
type: "Property";
key: Literal | Identifier; // 表示键
value: Expression; // 表示值
// 由于 ES5 语法中有 get/set 的存在,所以有 kind 来表示普通的初始化,或 get/set
kind: "init" | "get" | "set";
}

17-6、FunctionExpression

函数表达式节点。

interface FunctionExpression <: Function, Expression {
type: "FunctionExpression";
}

18、一元运算符表达式

18-1、UnaryExpression

一元运算表达式节点(++/-- 是 update 运算符,不在这个范畴内)

interface UnaryExpression <: Expression {
type: "UnaryExpression";
operator: UnaryOperator; // 表示运算符
prefix: boolean; // 表示是否为前缀运算符
argument: Expression; // 要执行运算的表达式
}

18-2、UnaryOperator

一元运算符,枚举类型,所有值如下:

enum UnaryOperator {
"-" | "+" | "!" | "~" | "typeof" | "void" | "delete"
}

18-3、UpdateExpression

update 运算表达式节点,即 ++/--,和一元运算符类似,只是 operator 指向的节点对象类型不同,这里是 update 运算符。

interface UpdateExpression <: Expression {
type: "UpdateExpression";
operator: UpdateOperator;
argument: Expression;
prefix: boolean;
}

18-4、UpdateOperator

update 运算符,值为 ++--,配合 update 表达式节点的 prefix 属性来表示前后。

enum UpdateOperator {
"++" | "--"
}

19、二元运算符表达式

19-1、BinaryExpression

二元运算表达式节点。

interface BinaryExpression <: Expression {
type: "BinaryExpression";
operator: BinaryOperator; // 二元运算符
left: Expression; // 运算符左侧表达式
right: Expression; // 运算符右侧表达式
}

BinaryOperator 二元运算符,所有值如下:

enum BinaryOperator {
"==" | "!=" | "===" | "!=="
| "<" | "<=" | ">" | ">="
| "<<" | ">>" | ">>>"
| "+" | "-" | "*" | "/" | "%"
| "|" | "^" | "&" | "in"
| "instanceof"
}

19-2、AssignmentExpression

赋值表达式节点。

interface AssignmentExpression <: Expression {
type: "AssignmentExpression";
operator: AssignmentOperator; // 赋值运算符
left: Pattern | Expression; // 赋值运算符左侧表达式
right: Expression; // 赋值运算符右侧表达式
}

AssignmentOperator 赋值运算符,所有值如下:

enum AssignmentOperator {
"=" | "+=" | "-=" | "*=" | "/=" | "%="
| "<<=" | ">>=" | ">>>="
| "|=" | "^=" | "&="
}

19-3、LogicalExpression

逻辑运算表达式节点,和赋值或者二元运算类型,只不过 operator 是逻辑运算符类型。

interface LogicalExpression <: Expression {
type: "LogicalExpression";
operator: LogicalOperator;
left: Expression;
right: Expression;
}

LogicalOperator 逻辑运算符,两种值,即与或。

enum LogicalOperator {
"||" | "&&"
}

19-4、MemberExpression

成员表达式节点。

interface MemberExpression <: Expression, Pattern {
type: "MemberExpression";
object: Expression; // 引用对象的表达式节点
property: Expression; // 表示属性名称
// computed 为 false 表示 . 来引用成员,是一个 Identifier 节点
// computed 为 true 表示 [] 来进行引用,是一个 Expression 节点,名称是表达式的结果值
computed: boolean;
}

20、其他表达式

20-1、ConditionalExpression

条件表达式,通常称为三元运算表达式,即 boolean ? true : false。属性参考条件语句。

interface ConditionalExpression <: Expression {
type: "ConditionalExpression";
test: Expression;
alternate: Expression;
consequent: Expression;
}

20-2、CallExpression

函数调用表达式。

interface CallExpression <: Expression {
type: "CallExpression";
callee: Expression; // 一个表达式节点,表示函数
arguments: [ Expression ]; // 是一个数组,元素是表达式节点,表示函数参数列表
}

20-3、NewExpression

new 表达式。

interface NewExpression <: CallExpression {
type: "NewExpression";
}

20-4、SequenceExpression

逗号运算符构建的表达式。

interface SequenceExpression <: Expression {
type: "SequenceExpression";
expressions: [ Expression ]; // 一个数组,即表示构成整个表达式,被逗号分割的多个表达式
}

20-5、Patterns

模式,主要在 ES6 的解构赋值中有意义,在 ES5 中,可以理解为和 Identifier 差不多的东西。

interface Pattern <: Node { }

AST 节点详情具体可查看 ESTree