AST 是 Abstract Syntax Tree 的缩写,即“形象语法树”. 它是以树状的模式体现编程语言的语法结构. webpack 打包 JS 代码的时候,webpack 会在咱们的原有代码根底上新增一些代码,例如咱们能够在打包 JS 代码的时候将高级代码转为低级代码,就是通过 AST 语法树来实现的
AST 在线生成地址
babel 插件查看应用地址
AST 生成过程由源码 -> 词法剖析 -> 语法分析 -> 形象语法树
例如 let a = 1 + 2
词法剖析
- 从左至右一个字符一个字符地读入源程序,从中辨认出一个一个“单词”“符号”等 读出来的就是一般的字符,没有任何编程语言的函数
- 将剖析之后后果保留在一个词法单元数组中
单词 | 单词 | 符号 | 数字 | 符号 | 数字 |
---|---|---|---|---|---|
let | a | = | 1 | + | 2 |
[{"type": "word", value: "let"},
{"type": "word", value: "a"},
{"type": "Punctuator", value: "="},
{"type": "Numberic", value: "1"},
{"type": "Punctuator", value: "+"},
{"type": "Numberic", value: "2"},
]
之后进入词法剖析
语法分析
将单词序列组合成各类的语法短语
关键字 | 标识符 | 赋值运算符 | 字面量 | 二元运算符 | 字面量 |
---|---|---|---|---|---|
let | a | = | 1 | + | 2 |
[{
"type": "VariableDecLaration",
"content": {{"type": "kind", "value": "let"}, // kind 示意是什么类型的申明
{"type": "Identifier", "value": "a"}, // Identifier 示意是标识符
{"type": "init", "value": "="}, // 示意初始值的表达式
{"type": "Literal", "value": "1"}, // Literal 示意是一个字面量
{"type": "operator", "value": "+"}, // operator 示意是一个二元运算符
{"type": "Literal", "value": "2"},
}
}]
形象语法树
"program": {
"type": "Program",
"start": 0,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 13
}
},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 13
}
},
"declarations": [ // 这里是数组,示意能够同时申明多个变量
{
"type": "VariableDeclarator",
"start": 4,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 13
}
},
"id": {
"type": "Identifier",
"start": 4,
"end": 5,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 5
},
"identifierName": "a"
},
"name": "a"
},
"init": {
"type": "BinaryExpression",
"start": 8,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 1,
"column": 13
}
},
"left": {
"type": "NumericLiteral",
"start": 8,
"end": 9,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 1,
"column": 9
}
},
"extra": {
"rawValue": 1,
"raw": "1"
},
"value": 1
},
"operator": "+",
"right": {
"type": "NumericLiteral",
"start": 12,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 12
},
"end": {
"line": 1,
"column": 13
}
},
"extra": {
"rawValue": 2,
"raw": "2"
},
"value": 2
}
}
}
],
"kind": "let"
}
],
"directives": []}
程序示例
package.json
依赖
"@babel/generator": "^7.11.6", // 转换 AST 构造
"@babel/parser": "^7.11.5", // 拆解 AST 树结构
"@babel/traverse": "^7.11.5",
"@babel/types": "^7.11.5",
@babel/parse 生成 AST
import * as parser from '@babel/parser'
const code = `let a = 1 + 2`
const ast = parser.parse(code)
console.log(ast)
批改语法树
通过 babel 的 traverse 模块遍历节点,找到对应节点后,通过 babel 中的 generator 模块来转换语法树为代码
import * as parser from '@babel/parser'
import traverse from "@babel/traverse"
import generator from '@babel/generator'
const code = `let val = 1 + 2`
const ast = parser.parse(code)
console.log(ast)
// traverse 办法能够遍历所有的语法树结点
traverse(ast, {enter(path) { // 这个 path 会找到所有的 node
if (path.node.type == 'Identifier') {
path.node.name = 'modify'
path.stop()}
}
})
const ret = generator(ast)
console.log(ret)
这里会把 val 批改为 modify
创立语法树
通过@babel/types
模块创立语法树节点而后 push 到 body 中就能够实现语法树的手动创立,
import * as parser from '@babel/parser'
import traverse from "@babel/traverse"
import generator from '@babel/generator'
import * as t from '@babel/types'
let code = ``
let ast = parser.parse(code)
let left = t.NumericLiteral(1)
let right = t.NumericLiteral(2)
let init = t.binaryExpression("+", left, right)
let id = t.identifier("add")
let variable = t.variableDeclarator(id, init)
let declaration = t.variableDeclaration('let', [variable])
ast.program.body.push(declaration)
// 转换为 code
let genCode = generator(ast)
console.log(genCode.code)
删除语法树节点
想要删除语法节点的外围就是先遍历找到所有的节点,通过 @babel/traverse
来实现,找到每个节点之后就能够通过具体的办法来实现增删改查操作
- NodePath 罕用的属性
-
- node:获取以后节点
- parent:父节点
- parentPath:父 path
- scope:作用域
- context:上下文
- NodePath 罕用的办法
-
- get:以后节点
- findParent:向父节点搜查节点
- getSibling:获取兄弟节点
- replaceWith:用 AST 节点替换该节点
- replaceWithMultiple:用多个 AST 节点替换该节点
- insertBefore:在节点前插入节点
- insertAfter:在节点后插入节点
- remove: 删除节点
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const generator = require('@babel/generator').default
let code = `
console.log('jake')
let sum = 1 + 2
let minus = 2 - 1
console.log("tom")
`
let ast = parser.parse(code)
console.log(ast)
// traverse 遍历语法树的
traverse(ast, {Identifier(path) {if (path.node.name == 'sum') {path.parentPath.remove()
}
}
})
console.log(generator(ast))
删除了 sum
之后