做者:陳曉強javascript
平常工做中,咱們會碰到js代碼解析的場景,好比分析代碼中require了哪些包,有些什麼關鍵API調用,大部分狀況使用正則表達式來處理,可一旦場景複雜,或者依賴於代碼上下文時,正則就很難處理了,這時候就要用到抽象語法樹。常見的uglify、eslint、babel、webpack等等都是基於抽象語法樹來處理的,如此強大,有必要好好了解一下。java
抽象語法樹即:Abstract Syntax Tree。簡稱AST,見下圖。node
圖中樹狀數據結構即AST,從這個過程能夠看到將代碼轉成AST後,經過操做節點來改變代碼。webpack
得到抽象語法樹的過程爲:代碼 => 詞法分析 => 語法分析 => AST
詞法分析
:把字符串形式的代碼轉換爲令牌(tokens)流。
語法分析
:把一個令牌流轉換成 AST 的形式。這個階段會使用令牌中的信息把它們轉換成一個 AST 的表述結構,這樣更易於後續的操做。
以下圖,代碼爲一個簡單的函數聲明。詞法分析階段,將代碼做爲字符串輸入得到關鍵詞,圖中function
、square
、(
、)
、{
、}
等都被識別爲關鍵詞(稍微回憶下編譯原理,字符挨個入棧,符合必定規則即出棧)。語法分析階段,對關鍵詞的組合造成一個個節點,如n*n這3個關鍵詞組合成二元表達式
,關鍵詞return與二元表達式組合成return語句
。最後組合成一個函數聲明語句
。git
如何得到AST已經簡單介紹了,那AST最終應該以什麼樣的數據結構存在呢,先看看上述函數聲明的AST結構github
那解析的依據是什麼,爲何要以上圖的結構出現,業界已經有了一套成熟的規範。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
es5.md
爲ES5規範,僅列出ES5的內容,
es2015.md
爲ES6規範,但只列出了針對ES5新增的內容,依次類推,最後的
es2019.md
即ES10是對ES9的補充,僅有一條規則。
打開最基礎的es5.md
,能夠看到全部語法基礎,這裏跟你們一塊兒讀一下大類,細分類別就略過了。讀規範時可使用https://astexplorer.net/ 輔助閱讀,能夠實時輸出AST。npm
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
}
複製代碼
interface Program <: Node {
type: "Program";
body: [ Directive | Statement ];
}
複製代碼
一棵完整的程序代碼樹,通常做爲根節點
interface Identifier <: Expression, Pattern {
type: "Identifier";
name: string;
}
複製代碼
標識符,咱們寫代碼時自定義的名稱,如變量名、函數名、屬性名。
interface Literal <: Expression {
type: "Literal";
value: string | boolean | null | number | RegExp;
}
複製代碼
字面量,如「hello」
、true
、null
、100
、/\d/
這些,注意字面量自己也是一個表達式語句(ExpressionStatement)
interface Function <: Node {
id: Identifier | null;
params: [ Pattern ];
body: FunctionBody;
}
複製代碼
一個函數聲明或者表達式,id是函數名,params是標識符數組,body是函數體,也是一個語句塊。
interface Statement <: Node { }
複製代碼
語句,子類有不少,塊語句
、if/switch語句
、return語句
、for/while語句
、with語句
等等
interface Declaration <: Statement { }
複製代碼
聲明,子類主要有變量申明、函數聲明。
interface Expression <: Node { }
複製代碼
表達式,子類不少,有二元表達式(n*n
)、函數表達式(var fun = function(){}
)、數組表達式(var arr = []
)、對象表達式(var obj = {}
)、賦值表達式(a = 1
)等等
interface Pattern <: Node { }
複製代碼
模式,主要在 ES6 的解構賦值中有意義(let {name}
= user,其中{name}部分爲ObjectPattern
),在 ES5 中,能夠理解爲和Identifier
差很少的東西。
經過以上規範解讀,知道了最終要生成的AST以什麼樣的結構存在,對於javascript的解析,業界已經有不少成熟的解析器,能夠將js代碼轉換成符合規範的AST
AST基礎篇介紹完畢,下篇將從實踐的角度繼續介紹
References
[1] Parser_API
[2] estree
[3] Parser_API(中文)
若是你以爲這篇內容對你有價值,請點贊,並關注咱們的官網和咱們的微信公衆號(WecTeam):