文章首發於: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}
]
});
複製代碼