文章首发于:github.com/USTB-musion…html
Babel,Webpack,vue-cli和esLint等不少的工具和库的核心都是经过Abstract Syntax Tree抽象语法树这个概念来实现对代码的检查、分析等操做的。在前端当中AST的使用场景很是广,好比在vue.js当中,咱们在代码中编写的template转化成render function的过程中第一步就是解析模版字符串生成AST。JS的许多语法为了给开发者更好的编程体验,并不适合不适合程序的理解。因此须要把源码转化为AST来更适合程序分析,浏览器的编译器通常会把源码转化为AST来进行进一步的分析来进行其余操做。经过了解AST这个概念,对深刻了解前端的一些框架和工具是颇有帮助的。前端
本文将从如下几部分进行总结:vue
在计算机科学中,抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。node
这是在线的AST转换器:AST转换器。代码转化成AST后的格式大体以下图所示: webpack
转化成AST以后的JSON格式大体为:git
{
"type": "Program",
"start": 0,
"end": 16,
"body": [
{
"type": "FunctionDeclaration",
"start": 0,
"end": 16,
"id": {
"type": "Identifier",
"start": 9,
"end": 12,
"name": "ast"
},
"expression": false,
"generator": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 14,
"end": 16,
"body": []
}
}
],
"sourceType": "module"
}
复制代码
字符串形式的 type 字段表示节点的类型。好比"BlockStatement","Identifier","BinaryExpression"等。 每一种类型的节点定义了一些属性来描述该节点类型。而后就能够经过这些节点来进行分析其余操做。github
JavaScript Parser,把js源码转化为抽象语法树的解析器。web
浏览器会把js源码经过解析器转为抽象语法树,再进一步转化为字节码或直接生成机器码。vue-cli
通常来讲每一个js引擎都会有本身的抽象语法树格式,Chrome的v8引擎,firefox的SpiderMonkey引擎等等,MDN提供了详细SpiderMonkey AST format的详细说明,算是业界的标准。express
let esprima = require('esprima');
let code = 'function ast(){}';
let ast = esprima.parse(code);
console.log(ast);
复制代码
经过npm i esprima -S安装以后,运行以上代码,会输出:
Script {
type: 'Program',
body:
[ FunctionDeclaration {
type: 'FunctionDeclaration',
id: [Identifier],
params: [],
body: [BlockStatement],
generator: false,
expression: false,
async: false } ],
sourceType: 'script' }
复制代码
let esprima = require('esprima');
let estraverse = require('estraverse');
let code = 'function ast(){}';
let ast = esprima.parse(code);
estraverse.traverse(ast, {
enter(node) {
console.log('enter', node.type)
if (node.type == 'Indentifier') {
node.name += 'enter';
}
},
leave(node) {
console.log('leave', node.type)
if (node.type == 'Indentifier') {
node.name += 'leave';
}
}
})
console.log(ast);
复制代码
经过npm i estraverse -S安装以后,运行以上代码,会输出:
Script {
type: 'Program',
body:
[ FunctionDeclaration {
type: 'FunctionDeclaration',
id: [Identifier],
params: [],
body: [BlockStatement],
generator: false,
expression: false,
async: false } ],
sourceType: 'script' }
复制代码
t esprima = require('esprima');
let estraverse = require('estraverse');
let escodegen = require('escodegen');
let code = 'function ast(){}';
let ast = esprima.parse(code);
estraverse.traverse(ast, {
enter(node) {
console.log('enter', node.type)
if (node.type == 'Identifier') {
node.name += '_enter';
}
},
leave(node) {
console.log('leave', node.type)
if (node.type == 'Identifier') {
node.name += '_leave';
}
}
});
let result = escodegen.generate(ast)
console.log(result);
复制代码
经过npm i escodegen -S安装完以后,执行以上代码,会输出:
function ast_enter_leave() {
}
复制代码
这样一来,就把
function ast() {
}
复制代码
修改成了:
function ast_enter_leave() {
}
复制代码
利用babel-core(babel核心库,实现核心的转换引擎)和babel-types(能够实现类型判断,生成AST节点等)和AST来将
let sum = (a, b) => a + b
复制代码
改为为:
let sum = function(a, b) {
return a + b
}
复制代码
实现代码以下:
// babel核心库,实现核心的转换引擎
let babel = require('babel-core');
// 能够实现类型判断,生成AST节点等
let types = require('babel-types');
let code = `let sum = (a, b) => a + b`;
// let sum = function(a, b) {
// return a + b
// }
// 这个访问者能够对特定类型的节点进行处理
let visitor = {
ArrowFunctionExpression(path) {
console.log(path.type);
let node = path.node;
let expression = node.body;
let params = node.params;
let returnStatement = types.returnStatement(expression);
let block = types.blockStatement([
returnStatement
]);
let func = types.functionExpression(null,params, block,false, false);
path.replaceWith(func);
}
}
let arrayPlugin = { visitor }
// babel内部会把代码先转成AST, 而后进行遍历
let result = babel.transform(code, {
plugins: [
arrayPlugin
]
})
console.log(result.code);
复制代码
实现代码以下:
// 预计算简单表达式的插件
let code = `const result = 1000 * 60 * 60`;
let babel = require('babel-core');
let types= require('babel-types');
let visitor = {
BinaryExpression(path) {
let node = path.node;
if (!isNaN(node.left.value) && ! isNaN(node.right.value)) {
let result = eval(node.left.value + node.operator + node.right.value);
result = types.numericLiteral(result);
path.replaceWith(result);
let parentPath = path.parentPath;
// 若是此表达式的parent也是一个表达式的话,须要递归计算
if (path.parentPath.node.type == 'BinaryExpression') {
visitor.BinaryExpression.call(null, path.parentPath)
}
}
}
}
let cal = babel.transform(code, {
plugins: [
{visitor}
]
});
复制代码