1. AST(抽象语法树) 到底是什么?
抽象语法树(Abstract Syntax Tree,AST)是源代码语法结构的一种抽象表示,它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
1.1 抽象语法树的具体结构
| 序号 | 类型原名称 | 中文名称 | 描述 |
|---|---|---|---|
| 1 | Program | 程序主体 | 整段代码的主体 |
| 2 | VariableDeclaration | 变量声明 | 声明一个变量,例如 var let const |
| 3 | FunctionDeclaration | 函数声明 | 声明一个函数,例如 function |
| 4 | ExpressionStatement | 表达式语句 | 通常是调用一个函数,例如 console.log() |
| 5 | BlockStatement | 块语句 | 包裹在 {} 块内的代码,例如 if (condition){var a = 1;} |
| 6 | BreakStatement | 中断语句 | 通常指 break |
| 7 | ContinueStatement | 持续语句 | 通常指 continue |
| 8 | ReturnStatement | 返回语句 | 通常指 return |
| 9 | SwitchStatement | Switch 语句 | 通常指 Switch Case 语句中的 Switch |
| 10 | IfStatement | If 控制流语句 | 控制流语句,通常指 if(condition){}else{} |
| 11 | Identifier | 标识符 | 标识,例如声明变量时 var identi = 5 中的 identi |
| 12 | CallExpression | 调用表达式 | 通常指调用一个函数,例如 console.log() |
| 13 | BinaryExpression | 二进制表达式 | 通常指运算,例如 1+2 |
| 14 | MemberExpression | 成员表达式 | 通常指调用对象的成员,例如 console 对象的 log 成员 |
| 15 | ArrayExpression | 数组表达式 | 通常指一个数组,例如 [1, 3, 5] |
| 16 | NewExpression | New 表达式 | 通常指使用 New 关键词 |
| 17 | AssignmentExpression | 赋值表达式 | 通常指将函数的返回值赋值给变量 |
| 18 | UpdateExpression | 更新表达式 | 通常指更新成员值,例如 i++ |
| 19 | Literal | 字面量 | 字面量 |
| 20 | BooleanLiteral | 布尔型字面量 | 布尔值,例如 true false |
| 21 | NumericLiteral | 数字型字面量 | 数字,例如 100 |
| 22 | StringLiteral | 字符型字面量 | 字符串,例如 vansenb |
| 23 | SwitchCase | Case 语句 | 通常指 Switch 语句中的 Case |
1.2 抽象语法树常见节点类型
2.如何获取AST
以前我们在做语文习题时,经常会做到的一种题型就是判断句子是否正确和改正,比如:"我是顺丰前端。"
解题方法通常是:
- 第一步:找出语句中的主语、谓语、宾语、定、状、补。
- 第二步:找出语句中的形容词、动词、标点符号等进行分析
2.1 分词后结构
在这一步骤中可以很明显的发现第一个错误:在句末使用的是一个句号,实际应该使用感叹号(!) 接着再对主语、谓语、宾语中的词语进行依次分析,将数据结构整理成这样(?):2.2 加入语法后结构
在上面这个简单的例子中,其实和AST的生成就颇为相似,AST是源代码的抽象语法结构的树状表现形式,简单点就是一个深度嵌套对象,这个对象能够描述我们书写代码的所有信息。
2.1 AST 如何生成
js 执行的第一步是读取 js 文件中的字符流,然后通过词法分析生成 token,之后再通过语法分析( Parser )生成 AST,最后生成机器码执行。
整个解析过程主要分为以下两个步骤:
- 词法分析:将整个代码字符串分割成最小语法单元数组
- 语法分析:在分词基础上建立分析语法单元之间的关系
词法分析
词法分析,也称之为扫描(scanner),简单来说就是调用 next() 方法,一个一个字母的来读取字符,然后与定义好的 JavaScript 关键字符做比较,生成对应的Token。Token 是一个不可分割的最小单元:
例如 var 这三个字符,它只能作为一个整体,语义上不能再被分解,因此它是一个 Token。
词法分析器里,每个关键字是一个 Token ,每个标识符是一个 Token,每个操作符是一个 Token,每个标点符号也都是一个 Token。除此之外,还会过滤掉源程序中的注释和空白字符(换行符、空格、制表符等。
最终,整个代码将被分割进一个tokens列表(或者说一维数组)。
语法分析
语法分析会将词法分析出来的 Token 转化成有语法含义的抽象语法树结构。同时,验证语法,语法如果有错的话,抛出语法错误。
获取AST常用工具: esprima,acron,espree等。
3. AST的常见应用
抽象语法树在代码语法的检查、代码风格的检查、代码的格式化、代码的高亮、代码错误提示、代码自动补全等等场景均有广泛的应用。
3.1 抽象语法树学术上的应用
(1) babel
Babel 其实就是一个最常用的Javascript编译器,它能够转译 ECMAScript 2015+ 的代码,使它在旧的浏览器或者环境中也能够运行,工作过程分为三个部分:
- Parse(解析) 将源代码转换成抽象语法树,树上有很多的estree节点
- Transform(转换) 对抽象语法树进行转换
- Generate(代码生成) 将上一步经过转换过的抽象语法树生成新的代码。
babel 实现原理及插件:
-
@babel/parser 可以把源码转换成
AST -
@babel/traverse 用于对
AST的遍历,维护了整棵树的状态,并且负责替换、移除和添加节点 -
@babel/generate 可以把
AST生成源码,同时生成sourcemap -
@babel/types 用于
AST节点的 Lodash 式工具库, 它包含了构造、验证以及变换AST节点的方法,对编写处理AST逻辑非常有用 -
@babel/core Babel 的编译器,核心 API 都在这里面,比如常见的
transform、parse,并实现了插件功能。
(2) eslint
相信大家在工作中都肯定使用过 ESLint,今天我来简述一下它的核心工作原理。
其实 Babel 里面的AST遍历也是有生命周期的,有两个钩子:在遍历开始之前或遍历结束之后,它们可以用于设置或清理/分析工作,其中visitor中包含对AST不同节点的处理方法。将需要配置的规则放在vistor中实现,以下是no-console的一个例子:
(3) AST的应用-插件按需加入
相信大家在工作中肯定都用过 Lodash 这个工具库,它是一个一致性、模块化、高性能的 JavaScript 实用工具库。
但是在使用它的时候有一个痛点,那就是它不支持按需加载,只要我们引用了这个工具库中的某个方法,就相当于引用整个工具库,要实现按需引入,就需要。
思路:针对import节点进行替换。
整体方案:
- 第一步:在插件中拿到我们在插件调用时传递的参数
libraryName - 第二步:获取
import节点,找出引入模块是libraryName的语句 - 第三步:进行批量替换旧节点