AST 抽象语法树详解

986 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

AST 可视化的网站

什么是 AST

我们知道,源代码在执行之前会经历三个步骤,分别是 词法分析语法分析代码生成 ,这三个步骤统称为“汇编”,而 AST 的生成主要便是依靠前两个步骤实现的:

  1. 词法分析

也叫扫描 scanner 。当词法分析源代码的时候,会①一个一个字母地读取源代码,当它遇到空格、操作符、或者特殊符号的时候,它会暂时停止扫描,②将扫描过的那部分合并成一个个的标识 token (这部分代码块就叫 词法单元 ),同时移除空白符、注释 等,③最后,整个代码被分割进一个 tokens 列表(或者说是一维数组)。

  1. 语法分析

这个过程会将词法分析出来的数组转化成树形的表达形式,这棵树被称为 抽象语法树 ,也就是 AST 。同时,语法分析时会验证语法,如果语法有异常的话,抛出语法错误。

当生成树的时候,解析器会删除一些没必要的标识 tokens (比如不完整的括号),因此 AST 不是 100% 与源代码匹配的。解析器 100%覆盖所有代码结构生成树叫做 CST (具体语法树)。

  1. 代码生成

这个过程会将 AST 转换成可执行代码。

总的来说,抽象语法树是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构。其实树状结构展示跟 json 格式的数据展示没什么区别,都是代码结构的转换,以另一种形式向我们展示代码。

AST 的用途

AST 的作用不仅仅是用来在 JavaScript 引擎的编译上,也广泛应用于我们实际开发中:

  • babel 插件 将 ES6 转化成 ES5
  • 使用 UglifyJS 来压缩代码
  • css 预处理器
  • 开发 WebPack 插件,如 babel 转义 jsx 语法、支持 es6 语法、antd 的按需加载
  • Vue-cli 前端自动化工具
  • 编辑器的代码高亮、自动补全、代码的语法检查
  • 项目的国际化

等等,这些底层原理都是基于AST来实现的。

初窥 AST

  • 举例 1:
var ast = 1;

image.png

  • 举例 2:
function test () {
  console.log('@你好', );
}

image.png

image.png

看了这两个例子,脑海中应该大致知道 AST “长得怎么样” 了,下面我们来具体分析图中的每个节点分别代表了什么含义。

常见的 AST 节点

AST 是对源码的抽象,字面量、标识符、表达式、语句、模块语法、class 语法 都有各自的 AST。

Literal(字面量)

序号举例字面量描述
1'foursheep'StringLiteral字符串字面量
2foursheepTemplateLiteral模板字面量
3123NumericLiteral数字字面量
4/^[a-z]+/RegExpLiteral正则表达式字面量
5TrueBooleanLiteral布尔字面量
6nullNullLiteral空值字面量
...

Identifier(标识符)

变量名、属性名、参数名等各种声明和引用的名字,都是 Identifer

Statement(语句)

语句是可以独立执行的单位,例如:breakcontinuereturndebugger 或者 if 语句while 语句for 语句 以及 声明语句、表达式语句等。每一条可以独立执行的代码,都可以被称之为 语句 ,即 Statement

序号举例语句描述
1break;BlockStatement块语句
2continue;ContinueStatement持续语句
3return;returnStatement返回语句
4debugger;DebuggerStatementDebugger语句
5throw Error();ThrowStatementThrow语句
6{}BlockStatement块语句
7try {} catch(e) {} finally{}TryStatementTry语句
8for (let key in obj) {}ForInStatementFor/In语句
9for (let i = 0;i < 10;i ++) {}ForStatementFor循环语句
10while (true) {}WhileStatementWhile循环语句
11do {} while (true)DoWhileStatementDo/While语句
12switch (v){case 1: break;default:;}SwitchStatementSwitch语句
13label: console.log();LabeledStatementLabel语句
14with (a){}WithStatementWith语句
15a=a+1ExpressionStatement表达式语句

Declaration(声明)

声明语句是一种特殊的语句,用于定义变量,它执行的逻辑是在作用域内声明一个变量、函数、class、import、export 等。

序号举例声明语句描述
1const a = 1;VariableDeclaration变量声明
2function b(){}FunctionDeclaration函数声明
3class C {}ClassDeclaration
4import d from 'e';ImportDeclaration
5export default e = 1;ExportDefaultDeclaration
6export {e};ExportNamedDeclaration
7export * from 'e';ExportAllDeclaration

Expression(表达式)

expression 是表达式,特点是执行完以后有返回值,这是和语句 (statement) 的区别。

序号举例表达式描述
1[1,2,3]ArrayExpression数组表达式
2a = 1AssignmentExpression赋值表达式
31 + 2;BinaryExpression二进制表达式/二元表达式
4-1;UnaryExpression一元表达式
5function(){};FunctionExpression函数表达式
6() => {};ArrowFunctionExpression箭头函数表达式
7class{};ClassExpressionclass 表达式
8aIdentifier标识符
9thisThisExpressionthis 表达式
10superSupersuper
11a::bBindExpression绑定表达式
12x.y()CallExpression调用表达式
13MemberExpression成员表达式
14new test()NewExpressionNew 表达式
15i++UpdateExpression更新表达式

模块语法

我们知道,导出模块有两种方式,分别是 importexport ,而这两种方式对应的 AST 节点也有所不同。

  1. import
  • named import:import {c, d} from 'c'; image.png

  • default import:import a from 'a'; image.png

  • namespaced import: import * as b from 'b'; image.png

这 3 种语法都对应 ImportDeclaration 节点,但是 specifiers 属性不同,分别对应 ImportSpicifierImportDefaultSpecifierImportNamespaceSpcifier

  1. export
  • named export:export { ast }; image.png

  • default export:export default a; image.png

  • all export:export * from 'ast'; image.png

这 3 种语法分别对应 ExportNamedDeclarationExportDefaultDeclarationExportAllDeclaration 的节点。其中只有 ExportNamedDeclaration 才有 specifiers 属性,其余两种都没有这部分。

class 语法

class 的语法比较特殊,有专门的 AST 节点来表示。

整个 class 的内容是 ClassBody ,属性是 ClassProperty ,方法是 ClassMethod(通过 kind 属性来区分是 constructor 还是 method )。

举例:

class Guang extends Person{
    name = 'guang';
    constructor() {}
    eat() {}
}

image.png

image.png

image.png

应用

参考资源

文章

视频