共计 4651 个字符,预计需要花费 12 分钟才能阅读完成。
前言
AST 形象语法树想必大家都有听过这个概念,然而不是只停留在听过这个层面呢。其实它对于编程来讲是一个十分重要的概念,当然也包含前端,在很多中央都能看见 AST 形象语法树的影子,其中不乏有 vue、react、babel、webpack、typeScript、eslint 等。简略来说凡是须要编译的中央你根本都能发现 AST 的存在。
babel
是用来将 javascript
高级语法编译成浏览器可能执行的语法,咱们能够从 babel
登程来理解 AST 形象语法树。
如果这篇文章有帮忙到你,❤️关注 + 点赞❤️激励一下作者,文章公众号首发,关注 前端南玖
第一工夫获取最新文章~
babel 编译流程
理解 AST 形象语法树之前咱们先来简略理解一下 babel
的编译流程,以及 AST 在 babel
编译过程中起到了什么作用?
我这里画了张图不便了解 babel
编译的整个流程
- parse: 用于将源代码编译成 AST 形象语法树
- transform: 用于对 AST 形象语法树进行革新
- generator: 用于将革新后的 AST 形象语法树转换成指标代码
很显著 AST 形象语法树
在这里充当了一个中间人的身份,作用就是能够通过对 AST 的操作还达到源代码到指标代码的转换过程,这将会比暴力应用正则匹配要优雅的多。
AST 形象语法树
在计算机科学中,形象语法树(Abstract Syntax Tree,AST) 是源代码语法结构的一种形象示意。它以树状的模式体现编程语言的语法结构,树上的每个节点都示意源代码中的一种构造。
尽管在日常业务中咱们可能很少会波及到 AST 层面,但如果你想在 babel
、webpack
等前端工程化上有所深度,AST 将是你深刻的根底。
预览 AST
说了这么多,那么 AST 到底长什么样呢?
接下来咱们能够通过工具 AST Explorer 来直观的感受一下!
比方咱们如下代码:
let fn = () => {console.log('前端南玖')
}
它最终生成的 AST 是这样的:
- AST 形象语法树是源代码语法结构的一种形象示意
- 每个蕴含 type 属性的数据结构,都是一个 AST 节点
- 它以树状的模式体现编程语言的语法结构,每个节点都示意源代码中的一种构造
AST 构造
为了对立
ECMAScript
规范的语法表白。社区中衍生出了 ESTree Spec,是目前前端所遵循的一种语法表白规范。
节点类型
类型 | 阐明 |
---|---|
File | 文件 (顶层节点蕴含 Program) |
Program | 整个程序节点 (蕴含 body 属性代表程序体) |
Directive | 指令 (例如 “use strict”) |
Comment | 代码正文 |
Statement | 语句 (可独立执行的语句) |
Literal | 字面量 (根本数据类型、简单数据类型等值类型) |
Identifier | 标识符 (变量名、属性名、函数名、参数名等) |
Declaration | 申明 (变量申明、函数申明、Import、Export 申明等) |
Specifier | 关键字 (ImportSpecifier、ImportDefaultSpecifier、ImportNamespaceSpecifier、ExportSpecifier) |
Expression | 表达式 |
公共属性
类型 | 阐明 |
---|---|
type | AST 节点的类型 |
start | 记录该节点代码字符串起始下标 |
end | 记录该节点代码字符串完结下标 |
loc | 内含 line、column 属性,别离记录开始完结的行列号 |
leadingComments | 开始的正文 |
innerComments | 两头的正文 |
trailingComments | 结尾的正文 |
extra | 额定信息 |
AST 是如何生成的
一般来讲生成 AST 形象语法树
都须要 javaScript 解析器来实现
JavaScript 解析器通常能够蕴含四个组成部分:
- 词法分析器(Lexical Analyser)
- 语法解析器(Syntax Parser)
- 字节码生成器(Bytecode generator)
- 字节码解释器(Bytecode interpreter)
词法剖析
这里次要是对代码字符串进行扫描,而后与定义好的 JavaScript 要害字符做比拟,生成对应的 Token。Token 是一个不可分割的最小单元。
词法分析器里,每个关键字是一个 Token,每个标识符是一个 Token,每个操作符是一个 Token,每个标点符号也都是一个 Token,词法剖析过程中不会关怀单词与单词之间的关系.
除此之外,还会过滤掉源程序中的正文和空白字符、换行符、空格、制表符等。最终,整个代码将被宰割进一个 tokens 列表
javaScript 中常见的 token
次要有:
关键字:var、let、const 等
标识符:没有被引号括起来的间断字符,可能是一个变量,也可能是 if、else 这些关键字,又或者是 true、false 这些内置常量
运算符:+、-、*、/ 等
数字:像十六进制,十进制,八进制以及迷信表达式等
字符串:变量的值等
空格:间断的空格,换行,缩进等
正文:行正文或块正文都是一个不可拆分的最小语法单元
标点:大括号、小括号、分号、冒号等
比方咱们还是这段代码:
let fn = () => {console.log('前端南玖')
}
它在通过词法剖析后生成的 token 是这样的:
工具:esprima
[
{
"type": "Keyword",
"value": "let"
},
{
"type": "Identifier",
"value": "fn"
},
{
"type": "Punctuator",
"value": "="
},
{
"type": "Punctuator",
"value": "("},
{
"type": "Punctuator",
"value": ")"
},
{
"type": "Punctuator",
"value": "=>"
},
{
"type": "Punctuator",
"value": "{"},
{
"type": "Identifier",
"value": "console"
},
{
"type": "Punctuator",
"value": "."
},
{
"type": "Identifier",
"value": "log"
},
{
"type": "Punctuator",
"value": "("},
{
"type": "String",
"value": "'前端南玖'"
},
{
"type": "Punctuator",
"value": ")"
},
{
"type": "Punctuator",
"value": "}"
}
]
拆分进去的每个字符都是一个 token
语法分析
这个过程也称为解析,是将词法剖析产生的 token
依照某种给定的模式文法转换成 AST
的过程。也就是把单词组合成句子的过程。在转换过程中会验证语法,语法如果有错的话,会抛出语法错误。
还是下面那段代码,在通过语法分析后生成的 AST 是这样的:
工具:AST Explorer
{
"type": "VariableDeclaration", // 节点类型:变量申明
"declarations": [ // 申明
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier", // 标识符
"name": "fn" // 变量名
},
"init": {
"type": "ArrowFunctionExpression", // 箭头函数表达式
"id": null,
"generator": false,
"async": false,
"params": [], // 函数参数
"body": { // 函数体
"type": "BlockStatement", // 语句块
"body": [
{
"type": "ExpressionStatement", // 表达式语句
"expression": {
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"identifierName": "console"
},
"name": "console"
},
"computed": false,
"property": {
"type": "Identifier",
"name": "log"
}
},
"arguments": [ // 函数参数
{
"type": "StringLiteral", // 字符串
"extra": {
"rawValue": "前端南玖",
"raw": "'前端南玖'"
},
"value": "前端南玖"
}
]
}
],
"directives": []}
}
}
],
"kind": "let" // 变量申明类型
}
在失去 AST 形象语法树之后,咱们就能够通过革新 AST 语法树来转换成本人想要生成的指标代码。
常见的解析器
- Esprima
第一个用 JavaScript 编写的合乎 EsTree 标准的 JavaScript 的解析器,后续多个编译器都是受它的影响
- acorn
一个玲珑、疾速的 JavaScript 解析器,齐全用 JavaScript 编写
- @babel/parser(Babylon)
babel 官网的解析器,最后 fork 于 acorn,起初齐全走向了本人的路线,从 babylon 改名之后,其构建的插件体系十分弱小
- UglifyJS
UglifyJS 是一个 JavaScript 解析器、放大器、压缩器和丑化器工具包。
- esbuild
esbuild 是用 go 编写的下一代 web 打包工具,它领有目前最快的打包记录和压缩记录,snowpack 和 vite 的也是应用它来做打包工具,为了谋求卓越的性能,目前没有将 AST 进行裸露,也无奈批改 AST,无奈用作解析对应的 JavaScript。
AST 利用
理解完 AST,你会发现咱们能够用它做许多简单的事件,咱们先来利用 @babel/core
简略实现一个移除 console 的插件来感受一下吧。
这个其实就是找法则,你只有晓得 console 语句在 AST 上是怎么体现的就可能通过这一特点准确找到所有的 console 语句并将其移出就好了。
- 先来看下 console 语句的 AST 长什么样
很显著它是一个表达式节点,所以咱们只须要找到 name 为 console 的表达式节点删除即可。
- 编写 plugin
const babel = require("@babel/core")
let originCode = `
let fn = () => {
const a = 1
console.log('前端南玖')
if(a) {console.log(a)
}else {return false}
}
`
let removeConsolePlugin = function() {
return {
// 拜访器
visitor: {CallExpression(path, state) {const { node} = path
if(node?.callee?.object?.name === 'console') {console.log('找到了 console 语句')
path.parentPath.remove()}
}
}
}
}
const options = {plugins: [removeConsolePlugin()]
}
let res = babel.transformSync(originCode, options)
console.dir(res.code)
从执行后果来看,它找到了两个 console 语句,并且都将它们移除了
这就是对 AST 的简略利用,学会 AST 能做的远不止这些像前端大部分比拟高级的内容都能看到它的存在。前面会持续更新 Babel 以及插件的用法。
原文首发地址点这里,欢送大家关注公众号 「前端南玖」,如果你想进前端交换群一起学习,请点这里
我是南玖,咱们下期见!!!