AST 可视化的网站
什么是 AST
我们知道,源代码在执行之前会经历三个步骤,分别是 词法分析 、语法分析 、代码生成 ,这三个步骤统称为“汇编”,而 AST
的生成主要便是依靠前两个步骤实现的:
- 词法分析
也叫扫描 scanner 。当词法分析源代码的时候,会①一个一个字母地读取源代码,当它遇到空格、操作符、或者特殊符号的时候,它会暂时停止扫描,②将扫描过的那部分合并成一个个的标识 token (这部分代码块就叫 词法单元 ),同时移除空白符、注释 等,③最后,整个代码被分割进一个 tokens 列表(或者说是一维数组)。
- 语法分析
这个过程会将词法分析出来的数组转化成树形的表达形式,这棵树被称为 抽象语法树 ,也就是 AST 。同时,语法分析时会验证语法,如果语法有异常的话,抛出语法错误。
当生成树的时候,解析器会删除一些没必要的标识 tokens (比如不完整的括号),因此 AST 不是 100% 与源代码匹配的。解析器 100%覆盖所有代码结构生成树叫做 CST (具体语法树)。
- 代码生成
这个过程会将 AST 转换成可执行代码。
总的来说,抽象语法树是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构。其实树状结构展示跟 json 格式的数据展示没什么区别,都是代码结构的转换,以另一种形式向我们展示代码。
AST 的用途
AST 的作用不仅仅是用来在 JavaScript 引擎的编译上,也广泛应用于我们实际开发中:
- babel 插件 将
ES6
转化成ES5
- 使用
UglifyJS
来压缩代码 - css 预处理器
- 开发 WebPack 插件,如 babel 转义 jsx 语法、支持 es6 语法、antd 的按需加载
Vue-cli
前端自动化工具- 编辑器的代码高亮、自动补全、代码的语法检查
- 项目的国际化
等等,这些底层原理都是基于AST来实现的。
初窥 AST
- 举例 1:
var ast = 1;
- 举例 2:
function test () {
console.log('@你好', );
}
看了这两个例子,脑海中应该大致知道 AST “长得怎么样” 了,下面我们来具体分析图中的每个节点分别代表了什么含义。
常见的 AST 节点
AST 是对源码的抽象,字面量、标识符、表达式、语句、模块语法、class 语法 都有各自的 AST。
Literal(字面量)
序号 | 举例 | 字面量 | 描述 |
---|---|---|---|
1 | ‘foursheep’ | StringLiteral |
字符串字面量 |
2 | foursheep |
TemplateLiteral |
模板字面量 |
3 | 123 | NumericLiteral |
数字字面量 |
4 | /^[a-z]+/ | RegExpLiteral |
正则表达式字面量 |
5 | True | BooleanLiteral |
布尔字面量 |
6 | null | NullLiteral |
空值字面量 |
… |
Identifier(标识符)
变量名、属性名、参数名等各种声明和引用的名字,都是 Identifer
。
Statement(语句)
语句是可以独立执行的单位,例如:break
、continue
、return
、debugger
或者 if 语句
、while 语句
、for 语句
以及 声明语句、表达式语句等。每一条可以独立执行的代码,都可以被称之为 语句 ,即 Statement
。
序号 | 举例 | 语句 | 描述 |
---|---|---|---|
1 | break; | BlockStatement |
块语句 |
2 | continue; | ContinueStatement |
持续语句 |
3 | return; | returnStatement |
返回语句 |
4 | debugger; | DebuggerStatement |
Debugger语句 |
5 | throw Error(); | ThrowStatement |
Throw语句 |
6 | {} | BlockStatement |
块语句 |
7 | try {} catch(e) {} finally{} | TryStatement |
Try语句 |
8 | for (let key in obj) {} | ForInStatement |
For/In语句 |
9 | for (let i = 0;i < 10;i ++) {} | ForStatement |
For循环语句 |
10 | while (true) {} | WhileStatement |
While循环语句 |
11 | do {} while (true) | DoWhileStatement |
Do/While语句 |
12 | switch (v){case 1: break;default:;} | SwitchStatement |
Switch语句 |
13 | label: console.log(); | LabeledStatement |
Label语句 |
14 | with (a){} | WithStatement |
With语句 |
15 | a=a+1 | ExpressionStatement |
表达式语句 |
Declaration(声明)
声明语句是一种特殊的语句,用于定义变量,它执行的逻辑是在作用域内声明一个变量、函数、class、import、export 等。
序号 | 举例 | 声明语句 | 描述 |
---|---|---|---|
1 | const a = 1; | VariableDeclaration |
变量声明 |
2 | function b(){} | FunctionDeclaration |
函数声明 |
3 | class C {} | ClassDeclaration |
|
4 | import d from ‘e’; | ImportDeclaration |
|
5 | export default e = 1; | ExportDefaultDeclaration |
|
6 | export {e}; | ExportNamedDeclaration |
|
7 | export * from ‘e’; | ExportAllDeclaration |
Expression(表达式)
expression 是表达式,特点是执行完以后有返回值,这是和语句 (statement) 的区别。
序号 | 举例 | 表达式 | 描述 |
---|---|---|---|
1 | [1,2,3] | ArrayExpression |
数组表达式 |
2 | a = 1 | AssignmentExpression |
赋值表达式 |
3 | 1 + 2; | BinaryExpression |
二进制表达式/二元表达式 |
4 | -1; | UnaryExpression |
一元表达式 |
5 | function(){}; | FunctionExpression |
函数表达式 |
6 | () => {}; | ArrowFunctionExpression |
箭头函数表达式 |
7 | class{}; | ClassExpression |
class 表达式 |
8 | a | Identifier |
标识符 |
9 | this | ThisExpression |
this 表达式 |
10 | super | Super |
super |
11 | a::b | BindExpression |
绑定表达式 |
12 | x.y() | CallExpression |
调用表达式 |
13 | MemberExpression |
成员表达式 | |
14 | new test() | NewExpression |
New 表达式 |
15 | i++ | UpdateExpression |
更新表达式 |
模块语法
我们知道,导出模块有两种方式,分别是 import
和 export
,而这两种方式对应的 AST 节点也有所不同。
import
named import:
import {c, d} from 'c';
![named import](AST-抽象语法树详解/named import.png)default import:
import a from 'a';
![default import](AST-抽象语法树详解/default import.png)namespaced import:
import * as b from 'b';
![namespaced import](AST-抽象语法树详解/namespaced import.png)
这 3 种语法都对应 ImportDeclaration
节点,但是 specifiers
属性不同,分别对应 ImportSpicifier
、ImportDefaultSpecifier
、ImportNamespaceSpcifier
。
export
named export:
export { ast };
![named export](AST-抽象语法树详解/named export.png)default export:
export default a;
![default export](AST-抽象语法树详解/default export.png)all export:
export * from 'ast';
![all export](AST-抽象语法树详解/all export.png)
这 3 种语法分别对应 ExportNamedDeclaration
、ExportDefaultDeclaration
、ExportAllDeclaration
的节点。其中只有 ExportNamedDeclaration
才有 specifiers
属性,其余两种都没有这部分。
class 语法
class 的语法比较特殊,有专门的 AST 节点来表示。
整个 class 的内容是 ClassBody
,属性是 ClassProperty
,方法是 ClassMethod
(通过 kind
属性来区分是 constructor
还是 method
)。
举例:
class Guang extends Person{
name = 'guang';
constructor() {}
eat() {}
}
![class ast-1](AST-抽象语法树详解/class ast-1.png)
![class ast-2](AST-抽象语法树详解/class ast-2.png)
![class ast-3](AST-抽象语法树详解/class ast-3.png)