做爲一個前端同窗,無論你是否知道AST
是個什麼東西,但一點也不影響你在工做中使用它。咱們平時項目中用到的less
、babel
、eslint
、代碼壓縮以及JavaScript
代碼可以運行在瀏覽器中等,都是創建在AST
的基礎上。在瞭解了AST
相關知識後,你也能夠本身折騰點東西出來,給單調無聊的工做找點樂趣。前端
AST(Abstract Syntax Tree)
,中文叫作抽象語法樹,是源代碼語法結構的一種抽象表示。它以樹狀的形式表現編程語言的語法結構,樹上的每一個節點都表示源代碼中的一種結構。之因此說語法"抽象"的,是由於這裏的語法並不會表示出真實語法中出現的每一個細節。好比,嵌套括號被隱含在樹的結構中,並無以節點的形式呈現;而相似於if-condition-then
這樣的條件跳轉語句,能夠使用帶有三個分支的節點來表示。(以上概念來自維基百科)。node
對於JavaScript
而言,能夠經過JS Parser
將JS
代碼轉換成AST
。目前比較常見的JS Parser
以下:jquery
本文中的例子均是使用esprima
來實現。webpack
在將代碼轉換成AST
的過程當中,有兩個重要的階段:詞法分析(Lexical Analysis)
和語法分析(Syntax Analysis)
。git
也稱爲分詞,是將字符串形式的代碼轉換爲標記(token)序列的過程。這裏的token
是一個字符串,是構成源代碼的最小單位,相似於英語中單詞。詞法分析也能夠理解成將英文字母組合成單詞的過程。詞法分析過程當中不會關心單詞之間的關係。好比:詞法分析過程當中可以將括號標記成token
,但並不會校驗括號是否匹配。github
JavaScript
中的token
主要包含如下幾種:web
關鍵字:var、let、const等編程
標識符:沒有被引號括起來的連續字符,多是一個變量,也多是 if、else 這些關鍵字,又或者是 true、false 這些內置常量瀏覽器
運算符: +、-、 *、/ 等babel
數字:像十六進制,十進制,八進制以及科學表達式等
字符串:變量的值等
空格:連續的空格,換行,縮進等
註釋:行註釋或塊註釋都是一個不可拆分的最小語法單元
標點:大括號、小括號、分號、冒號等
如下是const a = 'hello world'
通過esprima
詞法分析後生成的tokens
。
[ { "type": "Keyword", "value": "const" }, { "type": "Identifier", "value": "a" }, { "type": "Punctuator", "value": "=" }, { "type": "String", "value": "'hello world'" } ]
也稱爲解析器,是將詞法分析產生的token
按照某種給定的形式文法轉換成AST
的過程。也就是把單詞組合成句子的過程。在轉換過程當中會驗證語法,語法若是有錯的話,會拋出語法錯誤。
上述const a = 'hello world'
通過語法分析後生成的AST
以下:
{ "type": "Program", "body": [ { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": { "type": "Identifier", "name": "a" }, "init": { "type": "Literal", "value": "hello world", "raw": "'hello world'" } } ], "kind": "const" } ], "sourceType": "script" }
在拿到了AST
後,咱們就能夠分析AST
,在此基礎上作一些本身的事情。好比最簡單的將代碼中的某一變量都替換成另外一個名字。
下面咱們來實現將上述代碼中定義的變量a
替換成變量b
。要實現這個需求,咱們須要將源代碼轉換成AST
,而後在此基礎上進行一些操做,更改樹的內容,以後再把AST
轉換成目標代碼。也就是要經歷 解析 -> 轉換 -> 生成
的過程。
首先咱們須要分析源代碼生成的AST
和目標代碼生成的AST
具體有何不一樣。
如下是const b = 'hello world'
生成的AST:
{ "type": "Program", "body": [ { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": { "type": "Identifier", "name": "b" // 這裏不一樣 }, "init": { "type": "Literal", "value": "hello world", "raw": "'hello world'" } } ], "kind": "const" } ], "sourceType": "script" }
經過對比分析,發現惟一的不一樣就是type
爲Identifier
的id
的name
屬性值不同。接下來就能夠經過修改AST
來實現咱們的需求了。
咱們須要安裝estraverse(遍歷AST)和escodegen(根據AST生成JS)這兩個包。
const esprima = require('esprima'); const estraverse = require('estraverse'); const escodegen = require('escodegen'); const program = "const a = 'hello world'"; const ASTree = esprima.parseScript(program); estraverse.traverse(ASTree, { enter(node) { changeAToB(node); } }); const ASTreeAfterChange = escodegen.generate(tree); console.log(ASTreeAfterChange); // const b = 'hello world' function changeAToB(node) { if (node.type === 'Identifier') { node.name = 'b'; } }
看,是否是很容易就能夠實現。掌握了AST
的知識後,咱們能作不少事情,各類babel
的插件也是這麼產生的,只不過用的庫不同。
如何實現一個babel
插件能夠參考官方Babel 插件手冊