Javascript抽象語法樹上篇(基礎篇)

做者:陳曉強javascript

1、基礎

爲何要了解抽象語法樹

平常工做中,咱們會碰到js代碼解析的場景,好比分析代碼中require了哪些包,有些什麼關鍵API調用,大部分狀況使用正則表達式來處理,可一旦場景複雜,或者依賴於代碼上下文時,正則就很難處理了,這時候就要用到抽象語法樹。常見的uglify、eslint、babel、webpack等等都是基於抽象語法樹來處理的,如此強大,有必要好好了解一下。java

什麼是抽象語法樹

抽象語法樹即:Abstract Syntax Tree。簡稱AST,見下圖。node

  1. 圖中code先通過parse轉換成一個樹狀數據結構
  2. 接着對樹中節點進行轉換,圖中將葉子節點對換位置
  3. 將樹狀結構經過generate再生成code

圖中樹狀數據結構即AST,從這個過程能夠看到將代碼轉成AST後,經過操做節點來改變代碼。webpack

AST示例

如何得到抽象語法樹

得到抽象語法樹的過程爲:代碼 => 詞法分析 => 語法分析 => AST
詞法分析:把字符串形式的代碼轉換爲令牌(tokens)流。
語法分析:把一個令牌流轉換成 AST 的形式。這個階段會使用令牌中的信息把它們轉換成一個 AST 的表述結構,這樣更易於後續的操做。
以下圖,代碼爲一個簡單的函數聲明。詞法分析階段,將代碼做爲字符串輸入得到關鍵詞,圖中functionsquare(){}等都被識別爲關鍵詞(稍微回憶下編譯原理,字符挨個入棧,符合必定規則即出棧)。語法分析階段,對關鍵詞的組合造成一個個節點,如n*n這3個關鍵詞組合成二元表達式,關鍵詞return與二元表達式組合成return語句。最後組合成一個函數聲明語句git

語法分析

2、規範

如何得到AST已經簡單介紹了,那AST最終應該以什麼樣的數據結構存在呢,先看看上述函數聲明的AST結構github

AST結構

那解析的依據是什麼,爲何要以上圖的結構出現,業界已經有了一套成熟的規範。web

規範起源

在v8引擎以前,最先js引擎是SpiderMonkey,第一個版本由js做者Brendan Eich設計,後交給Mozilla組織維護。js引擎在執行js文件時,都會先將js代碼轉換成抽象語法樹(AST)。有一天,一位Mozilla工程師在FireFox中公開了這個將代碼轉成AST的解析器Api,也就是Parser_API[1],後來被人整理到github項目estree[2],慢慢的成了業界的規範。正則表達式

規範解讀

上面提到的Parser_API是規範的原文,中文版:Parser_API[3],但讀起來並不太友好,推薦直接讀整理後的git項目estree,打開項目地址,以下圖 express

estree

其中最下面的 es5.md爲ES5規範,僅列出ES5的內容, es2015.md爲ES6規範,但只列出了針對ES5新增的內容,依次類推,最後的 es2019.md即ES10是對ES9的補充,僅有一條規則。

打開最基礎的es5.md,能夠看到全部語法基礎,這裏跟你們一塊兒讀一下大類,細分類別就略過了。讀規範時可使用https://astexplorer.net/ 輔助閱讀,能夠實時輸出AST。npm

Node objects

interface Node {
    type: string;
    loc: SourceLocation | null;
}
複製代碼

定義AST中節點基本類型,其餘全部具體節點都須要實現以上接口,即每一個節點都必須包含type、loc兩個字段

type字段表示不一樣的節點類型,下邊會再講一下各個類型的狀況,分別對應了 JavaScript 中的什麼語法。你能夠從這個字段看出這個節點實現了哪一個接口
loc字段表示源碼的位置信息,若是沒有相關信息的話爲 null,不然是一個對象,包含了開始和結束的位置。接口以下

interface SourceLocation {
    source: string | null;
    start: Position;
    end: Position;
}
複製代碼

每一個 Position 對象包含了行(從1開始)和列(從0開始)信息,接口以下

interface Position {
    line: number; // >= 1
    column: number; // >= 0
}
複製代碼

Programs

interface Program <: Node {
    type: "Program";
    body: [ Directive | Statement ];
}
複製代碼

一棵完整的程序代碼樹,通常做爲根節點

Identifier

interface Identifier <: Expression, Pattern {
    type: "Identifier";
    name: string;
}
複製代碼

標識符,咱們寫代碼時自定義的名稱,如變量名、函數名、屬性名。

Literal

interface Literal <: Expression {
    type: "Literal";
    value: string | boolean | null | number | RegExp;
}
複製代碼

字面量,如「hello」truenull100/\d/這些,注意字面量自己也是一個表達式語句(ExpressionStatement)

Functions

interface Function <: Node {
    id: Identifier | null;
    params: [ Pattern ];
    body: FunctionBody;
}
複製代碼

一個函數聲明或者表達式,id是函數名,params是標識符數組,body是函數體,也是一個語句塊。

Statements

interface Statement <: Node { }
複製代碼

語句,子類有不少,塊語句if/switch語句return語句for/while語句with語句等等

Declarations

interface Declaration <: Statement { }
複製代碼

聲明,子類主要有變量申明、函數聲明。

Expressions

interface Expression <: Node { }
複製代碼

表達式,子類不少,有二元表達式(n*n)、函數表達式(var fun = function(){})、數組表達式(var arr = [])、對象表達式(var obj = {})、賦值表達式(a = 1)等等

Patterns

interface Pattern <: Node { }
複製代碼

模式,主要在 ES6 的解構賦值中有意義(let {name} = user,其中{name}部分爲ObjectPattern),在 ES5 中,能夠理解爲和Identifier 差很少的東西。

3、現狀

經過以上規範解讀,知道了最終要生成的AST以什麼樣的結構存在,對於javascript的解析,業界已經有不少成熟的解析器,能夠將js代碼轉換成符合規範的AST

  • Esprima,比較經典,出現的比較早
  • Acorn,fork自Esprima,代碼更精簡。webpack使用acorn進行模塊解析
  • UglifyJS2,主要用於代碼壓縮
  • babylon,babel解析器,fork自Acorn,目前最新版本是babylon7,對應npm包@babel/parser
  • Espree,eslint默認的解析器,因爲遵循同一套規範,也可使用babel的解析器替代
  • flow、shift等等

AST基礎篇介紹完畢,下篇將從實踐的角度繼續介紹

References
[1] Parser_API
[2] estree
[3] Parser_API(中文)


若是你以爲這篇內容對你有價值,請點贊,並關注咱們的官網和咱們的微信公衆號(WecTeam):

WecTeam
相關文章
相關標籤/搜索