什么是抽象树?
在计算机科学中,抽象语法树(abstract syntax tree 或者缩写为 AST),或者语法树(syntax tree)
是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。树上的每个节点都表示源代码中的一种结构。
之所以说语法是「抽象」的,是因为这里的语法并不会表示出真实语法中出现的每个细节
我们常用的浏览器就是通过将 js 代码转化为抽象语法树来进行下一步的分析等其他操作。所以将 js 转化为抽象语法树更利于程序的分析
var
是一个关键字AST
是一个定义者=
是 Equal 等号的叫法有很多形式,在后面我们还会看到is tree
是一个字符串;
就是 Semicoion
首先一段代码转换成的抽象语法树是一个对象,该对象会有一个顶级的 type 属性 Program
;第二个属性是 body
是一个数组。
body
数组中存放的每一项都是一个对象,里面包含了所有的对于该语句的描述信息
type: 描述该语句的类型 --> 变量声明的语句
kind: 变量声明的关键字 --> var
declaration: 声明内容的数组,里面每一项也是一个对象
type: 描述该语句的类型
id: 描述变量名称的对象
type: 定义
name: 变量的名字
init: 初始化变量值的对象
type: 类型
value: 值 "is tree" 不带引号
row: ""is tree"" 带引号
词法分析和语法分析
JavaScript 是解释型语言,一般通过 词法分析 -> 语法分析 -> 语法树,就可以开始解释执行了
词法分析:也叫扫描,是将字符流转换为记号流(tokens),它会读取我们的代码然后按照一定的规则合成一个个的标识
比如说:var a = 2 ,这段代码通常会被分解成 var、a、=、2
;[
{ type: 'Keyword', value: 'var' },
{ type: 'Identifier', value: 'a' },
{ type: 'Punctuator', value: '=' },
{ type: 'Numeric', value: '2' },
]
使用场景
- 语法检查、代码风格检查、格式化代码、语法高亮、错误提示、自动补全等
- 代码混淆压缩
- 优化变更代码,改变代码结构等
在线astexplorer
选择 babel 库,使用最多的 ast 处理库
文档:https://babeljs.io/docs/en/
我们只用 https://www.babeljs.cn/docs/babel-parser 部分
AST 基础工具
安装
cnpm install @babel/parser
cnpm install @babel/traverse
cnpm install @babel/types
cnpm install @babel/generator
parse: 将 js 代码转化为 AST
generator: 将 AST 转化会 js 代码
traverse: 节点遍历操作的方便功能
types: 提供一个与格式相关的对象合集,可以操作从而生成节点
1、abel/parser
在babel中编译器插件是@babel/parser,其作用就是将源码转换为AST,使用前需要npm install @babel/parser,使用方法如下:
const {parse} = require("@babel/parser"); //require返回一个object,使用解构赋值,可以只取出对应名字的属性
const js_code = "const res = 'Rannie';";
const ast_code = parse(js_code);
console.log(ast_code.program.body[0].declarations[0].id.name);
console.log(ast_code.program.body[0].declarations[0].init.extra.rawValue);
2、abel/generator
其作用就是将转换好的 ast 重新生成代码。这样的代码就就可以安全的在浏览器运行。
const {parse} = require("@babel/parser");
const generate = require('@babel/generator').default
const js_code = `const res = 'Rannie';`;
const ast_code = parse(js_code);
const js_code2 = generate(ast_code).code
console.log(js_code2)
3、babel/traverse
这个插件的作用是对ast进行遍历parse,在迭代的过程中可以定义回调函数,回调函数的参数提供了丰富的增、删、改、查以及类型断言的方法,比如replaceWith/remove/find/isMemberExpression
const {parse} = require("@babel/parser"); //require返回一个object,使用解构赋值,可以只取出对应名字的属性
const traverse = require('@babel/traverse').default
const generate = require('@babel/generator').default
var js_code = `
a = 1;
a = 1;
a = 1;
`
const ast_code = parse(js_code)
const visitor = {
Literal(path){
path.node.value = 100;
console.log(path.node.value);
},
Identifier(path){
path.node.name = 'b'
}
}
traverse(ast_code, visitor);
const result_js_code = generate(ast_code).code
console.log(result_js_code)
const {parse} = require("@babel/parser"); //require返回一个object,使用解构赋值,可以只取出对应名字的属性
const traverse = require('@babel/traverse').default
const generate = require('@babel/generator').default
js_code = `function hello(ofN1){ console["\x6c\x6f\x67"](ofN1)};`
const ast_code = parse(js_code) // Literal
const visitor = {
StringLiteral(path){
path.node.extra.raw = '\'' + path.node.extra.rawValue + '\''
},
}
traverse(ast_code, visitor);
const result_js_code = generate(ast_code).code
console.log(result_js_code)