追本溯源
在使用前端許多工具外掛的時候,我們大多知道每個工具庫、每個外掛能做什麼,不過很多同學其實並不清楚背後用到的技術,如webpack、rollup、UglifyJS、Lint等很多的工具和庫的核心都是通過Abstract Syntax Tree 抽象語法樹這個概念來實現對程式碼的檢查、分析等操作的。通過了解抽象語法樹這個概念,你也可以隨手編寫類似的工具,發現一個新的世界。
Abstract Syntax Tree 抽象語法樹定義
理論的知識總是有些枯燥乏味,不過客官別急,一步一步來。
其實這些工具的原理都是通過JavaScript Parser把程式碼轉化為一顆抽象語法樹(AST),這顆樹定義了程式碼的結構,通過操縱這顆樹,我們可以精準的定位到宣告語句、賦值語句、運算語句等等,實現對程式碼的分析、優化、變更等操作。
wikipedia定義:
In computer science, an abstract syntax tree (AST), or just syntax tree, is a tree representation of the abstract syntactic structure of source code written in a programming language.
翻譯為:
在電腦科學中,抽象語法樹(abstract syntax tree或者縮寫為AST),或者語法樹(syntax tree),是原始碼的抽象語法結構的樹狀表現形式,這裡特指程式語言的原始碼。
Javascript的語法是為了給開發者更好的程式設計而設計的,但是不適合程式的理解。所以需要轉化為AST來更適合程式分析,瀏覽器編譯器一般會把原始碼轉化為AST來進行進一步的分析等其他操作。
以下只介紹Javascript相關的抽象語法樹
比如說有一段程式碼:
var a = 3;
a + 5;
複製程式碼
那麼它的抽象語法樹就類似:
JavaScript Parser
JavaScript Parser, 把js原始碼轉化為抽象語法樹的解析器。
瀏覽器會把js原始碼通過解析器轉為抽象語法樹,再進一步轉化為位元組碼或直接生成機器碼。
一般來說每個js引擎都會有自己的抽象語法樹格式,Chrome的v8引擎,firefox的SpiderMonkey引擎等等,MDN提供了詳細SpiderMonkey AST format的詳細說明,算是業界的標準。
發展到現在可能不同的JavaScript Parser的AST格式會不同,或基於SpiderMonkey AST format,或重新設計自己的AST format,或基於SpiderMonkey AST format優化改進。通過優化抽象語法樹,來使程式執行的更快,也是一種提高效率的方法。
常用的JavaScript Parser有:
Esprima
UglifyJS2
Traceur
Acorn
Shift
複製程式碼
在Esprima的官網有一個比較各個Parser解析速度的列表Speed Comparison。 看下來Acorn是公認的最快的。
UglifyJS2的作者自己實現了一套js的抽象語法樹,用到了繼承,和現有的扁平的抽象語法樹都有所不同,但作者也提供使用不同的抽象語法樹來解析程式碼。
AST explorer可以線上看到不同的parser解析js程式碼後得到的AST。
JavaScript AST visualizer 可以線上視覺化的看到AST。
生成並使用抽象語法樹 通過 esprima , 把一個名字為ast的空函式的原始碼生成一顆AST樹:
var esprima = require('esprima');
var code = 'function ast(){}';
var ast = esprima.parse(code);
複製程式碼
生成的抽象語法樹長這樣:
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "ast",
"range": [
9,
12
]
},
"params": [],
"body": {
"type": "BlockStatement",
"body": [],
"range": [
14,
16
]
},
"generator": false,
"expression": false,
"range": [
0,
16
]
}
],
"sourceType": "module",
"range": [
0,
16
]
}
複製程式碼
通過 estraverse 遍歷並且更新抽象語法樹,把函式名稱改為ast_awsome:
...
var estraverse = require('estraverse');
estraverse.traverse(ast, {
enter: function (node) {
node.name += "_awsome";
}
});
複製程式碼
通過 escodegen 將AST重新生成為原始碼:
...
var escodegen = require("escodegen");
var regenerated_code = escodegen.parse(ast)
複製程式碼
AST三板斧:
通過 esprima 把原始碼轉化為AST 通過 estraverse 遍歷並更新AST 通過 escodegen 將AST重新生成原始碼 抽象語法樹的用途 瀏覽器最先就會把原始碼解析為抽象語法樹,對瀏覽器而言AST的作用非常重要。
對開發者而言,AST的作用就是可以精準的定位到程式碼的任何地方,它就像是是你的手術刀,對程式碼進行一系列的操作。
常見的幾種用途:
程式碼語法的檢查、程式碼風格的檢查、程式碼的格式化、程式碼的高亮、程式碼錯誤提示、程式碼自動補全等等 如JSLint、JSHint對程式碼錯誤或風格的檢查,發現一些潛在的錯誤 IDE的錯誤提示、格式化、高亮、自動補全等等 程式碼混淆壓縮 UglifyJS2等 優化變更程式碼,改變程式碼結構使達到想要的結構 程式碼打包工具webpack、rollup等等 CommonJS、AMD、CMD、UMD等程式碼規範之間的轉化 CoffeeScript、TypeScript、JSX等轉化為原生Javascript 總結 抽象語法樹在前端領域中的應用廣泛,通過抽象語法樹大家可以實現很多功能,發現編寫工具提高效率帶來的樂趣。
參考文章
Abstract syntax tree
Understanding ASTs by Building Your Own Babel Plugin
UglifyJS — why not switching to SpiderMonkey AST
A Technical Comparison of the Shift and SpiderMonkey AST Formats
SpiderMonkey Parser API
轉載自 div.io/topic/1994?…, 感謝原作者