Abstract Syntax Tree 抽象語法樹簡介

jacintoface發表於2018-02-11

追本溯源

在使用前端許多工具外掛的時候,我們大多知道每個工具庫、每個外掛能做什麼,不過很多同學其實並不清楚背後用到的技術,如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;
複製程式碼

那麼它的抽象語法樹就類似:

'abc'

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?…, 感謝原作者

相關文章