这是什么?
AST : 全称为 Abstract Syntax Tree,意为抽象语法树,它是源代码语法结构的一种抽象表示。
为什么要了解?
虽然在平常业务中很少接触它,但是它无处不在,无论是代码编译(babel)、打包构建(webpack)、语法检查(eslint)、美化代码(prettier)、模版编译(jsx、.vue)这些都离不开AST。了解和学习AST能够更好去理解上面工具的工作原理,同时也能用来做一些工具去优化开发流程,提升开发效率
浅析AST
传统编译语言中,源代码执行会先经历三个阶段
- 词法分析阶段: 扫描代码,将字符组成的字符串分解成一个个代码块(词法单元token),vscode代码高亮就是这一步做的
- 语法分析阶段: 将词法单元流转换成一个由元素逐级嵌套组成的语法结构树,即所谓的抽象语法树
- 代码生成阶段:将 AST 转换成一系列可执行的机器指令代码
比如代码
add(a,b)
词法分析,生成tokens列表
生成token时候会一行行扫描代码(可看这篇分享由esprima源码tokens生成部分),这时候它会移除空白符,注释,等。最后,整个代码将被分割进一个tokens列表(或者说一维数组)结果实例
语法解析,生成AST树
- 语义分析则是将得到的词汇进行一个立体的组合,确定词语之间的关系
- 简单来说语法分析是对语句和表达式的识别,是一个递归的过程 具体过程可以看源码解释这里不在阐述 github.com/jamiebuilds…
可以使用这个网站看ast树astexplorer.net/
怎么去用?
三步曲
- 调用babel的parse解析成ast
- 调用babel的traverse遍历ast节点对相应的节点进行操作
- 调用babel的generator生成新的代码
场景1: 表达式编辑器希望点击服务端返回的函数,解析出其中的参数变成输入框给用户输入
找出函数参数
function getCodeArgs(code) {
let args = []
try {
const ast = parse(code)
const argMap = {}
traverse(ast, {
CallExpression(path) {
path.node.arguments.forEach((node, index) => {
if (node.type === 'Identifier') {
argMap[node.name] = {
name: node.name,
callExpression: path.node.name,
argIndex: index,
}
}
if (node.type === 'MemberExpression') {
const name = node.object.name + '.' + node.property.name
argMap[name] = {
name,
callExpression: path.node.name,
argIndex: index,
}
}
})
},
})
args = Object.keys(argMap)
} catch (error) {
console.log('error', error)
}
return args
}
把用户输入的值重新设置回ast相应的节点上
function generatorCodeForMap(code, dataMap) {
let str = ''
try {
const ast = parse(code)
traverse(ast, {
CallExpression(path) {
path.node.arguments.forEach((node) => {
if (node.type === 'Identifier' && dataMap[node.name]) {
node.name = `'${dataMap[node.name]}'`
}
if (node.type === 'MemberExpression') {
const name = node.object.name + '.' + node.property.name
if (dataMap[name]) {
node.type = 'Identifier'
node.name = `'${dataMap[name]}'`
}
}
})
},
})
const res = generator(ast, {}, code)
// 3. generator 将 AST 转回成代码
str = (res?.code || '').replace(/;(\s+)?$/, '')
} catch (error) {
console.log('error', error)
}
return str
}
场景2: 四则运算编辑器,用户输入1+(2*3-2)希望输出5
按照上面三步曲来
import forIn from 'lodash/forIn'
import { simpleArithmetic } from 'utils' // 简单两个数字运算
import { parse } from 'acorn'
const IS_NUMBER_REG = /^([-]?)\d+([.]\d+)?$/
const traverseAst = (node) => {
let res
switch (node.type) {
case 'ExpressionStatement':
res = simpleArithmetic(node.expression.operator, false, traverseAst(node.expression.left), traverseAst(node.expression.right))
break
case 'BinaryExpression':
res = simpleArithmetic(node.operator, false, traverseAst(node.left), traverseAst(node.right))
break
case 'UnaryExpression': {
if (node.argument.type === 'NumericLiteral') {
res = node.operator + node.argument.value
} else {
res = node.operator + traverseAst(node.argument)
}
break
}
case 'NumericLiteral':
case 'Literal':
res = node.value
break
default: break
}
return res
}
const complexEval = (input) => {
let str
try {
const ast = parse(input)
str = traverseAst(ast.body[0])
} catch (error) {
str = ''
}
return +Number(str).toFixed(4)
}
结语
感谢
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!