AST 抽象語法樹學習
Abstract Syntax Tree 抽象語法樹簡介
在使用前端許多工具外掛的時候,我們大多知道每個工具庫、每個外掛能做什麼,不過很多同學其實並不清楚背後用到的技術,如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。
生成並使用抽象語法樹
通過 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
總結
抽象語法樹在前端領域中的應用廣泛,通過抽象語法樹大家可以實現很多功能,發現編寫工具提高效率帶來的樂趣。