摘要:本文除了介紹的一些基本概念外,更偏重實戰(zhàn),講解如何利用它來對代碼進行修改。二基本概念全稱,也就是抽象語法樹,它是將編程語言轉(zhuǎn)換成機器語言的橋梁。四實戰(zhàn)下面我們來詳細看看如何對進行操作。
歡迎關(guān)注我的公眾號睿Talk,獲取我最新的文章:
最近突然對 AST 產(chǎn)生了興趣,深入了解后發(fā)現(xiàn)它的使用場景還真的不少,很多我們?nèi)粘i_發(fā)使用的工具都跟它息息相關(guān),如 Babel、ESLint 和 Prettier 等。本文除了介紹 AST 的一些基本概念外,更偏重實戰(zhàn),講解如何利用它來對代碼進行修改。
二、基本概念AST 全稱 Abstract Syntax Tree,也就是抽象語法樹,它是將編程語言轉(zhuǎn)換成機器語言的橋梁。瀏覽器在解析 JS 的過程中,會根據(jù) ECMAScript 標準將字符串進行分詞,拆分為一個個語法單元。然后再遍歷這些語法單元,進行語義分析,構(gòu)造出 AST。最后再使用 JIT 編譯器的全代碼生成器,將 AST 轉(zhuǎn)換為本地可執(zhí)行的機器碼。如下面一段代碼:
function add(a, b) { return a + b; }
進行分詞后,會得到這些 token:
對 token 進行分析,最終會得到這樣一棵 AST(簡化版):
{ "type": "Program", "body": [ { "type": "FunctionDeclaration", "id": { "type": "Identifier", "name": "add" }, "params": [ { "type": "Identifier", "name": "a" }, { "type": "Identifier", "name": "b" } ], "body": { "type": "BlockStatement", "body": [ { "type": "ReturnStatement", "argument": { "type": "BinaryExpression", "left": { "type": "Identifier", "name": "a" }, "operator": "+", "right": { "type": "Identifier", "name": "b" } } } ] } } ], "sourceType": "module" }
拿到 AST 后就可以根據(jù)規(guī)則轉(zhuǎn)換為機器碼了,在此不再贅述。
三、Babel 工作原理AST 除了可以轉(zhuǎn)換為機器碼外,還能做很多事情,如 Babel 就能通過分析 AST,將 ES6 的代碼轉(zhuǎn)換成 ES5。
Babel 的編譯過程分為 3 個階段:
解析:將代碼字符串解析成抽象語法樹
變換:對抽象語法樹進行變換操作
生成:根據(jù)變換后的抽象語法樹生成新的代碼字符串
Babel 實現(xiàn)了一個 JS 版本的解析器Babel parser,它能將 JS 字符串轉(zhuǎn)換為 JSON 結(jié)構(gòu)的 AST。為了方便對這棵樹進行遍歷和變換操作,babel 又提供了traverse工具函數(shù)。完成 AST 的修改后,可以使用generator生成新的代碼。
四、AST 實戰(zhàn)下面我們來詳細看看如何對 AST 進行操作。先建好如下的代碼模板:
import parser from "@babel/parser"; import generator from "@babel/generator"; import t from "@babel/types"; import traverser from "@babel/traverse"; const generate = generator.default; const traverse = traverser.default; const code = ``; const ast = parser.parse(code); // AST 變換 const output = generate(ast, {}, code); console.log("Input ", code); console.log("Output ", output.code);
構(gòu)造一個 hello world
打開 AST Explorer,將左側(cè)代碼清空,再輸入 hello world,可以看到前后 AST 的樣子:
// 空 { "type": "Program", "body": [], "sourceType": "module" } // hello world { "type": "Program", "body": [ { "type": "ExpressionStatement", "expression": { "type": "Literal", "value": "hello world", "raw": ""hello world"" }, "directive": "hello world" } ], "sourceType": "module" }
接下來通過代碼構(gòu)造這個ExpressionStatement:
const code = ``; const ast = parser.parse(code); // 生成 literal const literal = t.stringLiteral("hello world") // 生成 expressionStatement const exp = t.expressionStatement(literal) // 將表達式放入body中 ast.program.body.push(exp) const output = generate(ast, {}, code);
可以看到 AST 的創(chuàng)建過程就是自底向上創(chuàng)建各種節(jié)點的過程。這里我們借助 babel 提供的types對象幫我們創(chuàng)建各種類型的節(jié)點。更多類型可以查閱這里。
同樣道理,下面我們來看看如何構(gòu)造一個賦值語句:
const code = ``; const ast = parser.parse(code); // 生成 identifier const id = t.identifier("str") // 生成 literal const literal = t.stringLiteral("hello world") // 生成 variableDeclarator const declarator = t.variableDeclarator(id, literal) // 生成 variableDeclaration const declaration = t.variableDeclaration("const", [declarator]) // 將表達式放入body中 ast.program.body.push(declaration) const output = generate(ast, {}, code);
獲取 AST 中的節(jié)點
下面我們將對這段代碼進行操作:
export default { data() { return { count: 0 } }, methods: { add() { ++this.count }, minus() { --this.count } } }
假設(shè)我想獲取這段代碼中的data方法,可以直接這么訪問:
const dataProperty = ast.program.body[0].declaration.properties[0]
也可以使用 babel 提供的traverse工具方法:
const code = ` export default { data() { return { count: 0 } }, methods: { add() { ++this.count }, minus() { --this.count } } } `; const ast = parser.parse(code, {sourceType: "module"}); // const dataProperty = ast.program.body[0].declaration.properties[0] traverse(ast, { ObjectMethod(path) { if (path.node.key.name === "data") { path.node.key.name = "myData"; // 停止遍歷 path.stop(); } } }) const output = generate(ast, {}, code);
traverse方法的第二個參數(shù)是一個對象,只要提供與節(jié)點類型同名的屬性,就能獲取到所有的這種類型的節(jié)點。通過path參數(shù)能訪問到節(jié)點信息,進而找出需要操作的節(jié)點。上面的代碼中,我們找到方法名為data的方法后,將其改名為myData,然后停止遍歷,生成新的代碼。
替換 AST 中的節(jié)點
可以使用replaceWith和replaceWithSourceString替換節(jié)點,例子如下:
// 將 this.count 改成 this.data.count const code = `this.count`; const ast = parser.parse(code); traverse(ast, { MemberExpression(path) { if ( t.isThisExpression(path.node.object) && t.isIdentifier(path.node.property, { name: "count" }) ) { // 將 this 替換為 this.data path .get("object") .replaceWith( t.memberExpression(t.thisExpression(), t.identifier("data")) ); // 下面的操作跟上一條語句等價,更加直觀方便 // path.get("object").replaceWithSourceString("this.data"); } } }); const output = generate(ast, {}, code);
插入新的節(jié)點
可以使用pushContainer、insertBefore和insertAfter等方法來插入節(jié)點:
// 這個例子示范了 3 種節(jié)點插入的方法 const code = ` const obj = { count: 0, message: "hello world" } `; const ast = parser.parse(code); const property = t.objectProperty( t.identifier("new"), t.stringLiteral("new property") ); traverse(ast, { ObjectExpression(path) { path.pushContainer("properties", property); // path.node.properties.push(property); } }); /* traverse(ast, { ObjectProperty(path) { if ( t.isIdentifier(path.node.key, { name: "message" }) ) { path.insertAfter(property); } } }); */ const output = generate(ast, {}, code);
刪除節(jié)點
使用remove方法來刪除節(jié)點:
const code = ` const obj = { count: 0, message: "hello world" } `; const ast = parser.parse(code); traverse(ast, { ObjectProperty(path) { if ( t.isIdentifier(path.node.key, { name: "message" }) ) { path.remove(); } } }); const output = generate(ast, {}, code);五、總結(jié)
本文介紹了 AST 的一些基本概念,講解了如何使用 Babel 提供的 API,對 AST 進行增刪改查的操作。?掌握這項技能,再加上一點想象力,就能制作出實用的代碼分析和轉(zhuǎn)換工具。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/105902.html
摘要:生成屬性這一步,我們要先提取原函數(shù)中的的對象。所以這里我們還是主要使用來訪問節(jié)點獲取第一級的,也就是函數(shù)體將合并的寫法用生成生成生成插入到原函數(shù)下方刪除原函數(shù)程序輸出將中的屬性提升一級這里遍歷中的屬性沒有再采用,因為這里結(jié)構(gòu)是固定的。 ??經(jīng)過之前的三篇文章介紹,AST的CRUD都已經(jīng)完成。下面主要通過vue轉(zhuǎn)小程序過程中需要用到的部分關(guān)鍵技術(shù)來實戰(zhàn)。 下面的例子的核心代碼依然是最簡單...
摘要:操作通常配合來完成。因為是個數(shù)組,因此,我們可以直接使用數(shù)組操作自我毀滅方法極為簡單,找到要刪除的,執(zhí)行就結(jié)束了。如上述代碼,我們要刪除屬性,代碼如下到目前為止,的我們都介紹完了,下面一篇文章以轉(zhuǎn)小程序為例,我們來實戰(zhàn)一波。 ??通過前兩篇文章的介紹,大家已經(jīng)了解了Create和Retrieve,我們接著介紹Update和 Remove操作。Update操作通常配合Create來完成。...
摘要:抽象語法樹,是一個非?;A(chǔ)而重要的知識點,但國內(nèi)的文檔卻幾乎一片空白。事實上,在世界中,你可以認為抽象語法樹是最底層。通過抽象語法樹解析,我們可以像童年時拆解玩具一樣,透視這臺機器的運轉(zhuǎn),并且重新按著你的意愿來組裝。 抽象語法樹(AST),是一個非?;A(chǔ)而重要的知識點,但國內(nèi)的文檔卻幾乎一片空白。本文將帶大家從底層了解AST,并且通過發(fā)布一個小型前端工具,來帶大家了解AST的強大功能 ...
摘要:超出此時間則渲染錯誤組件。元素節(jié)點總共有種類型,為表示是普通元素,為表示是表達式,為表示是純文本。 實戰(zhàn) - 插件 form-validate {{ error }} ...
實踐是所有展示最好的方法,因此我覺得可以不必十分細致的,但我們的展示卻是整體的流程、輸入和輸出。現(xiàn)在我們就看看Vue 的指令、內(nèi)置組件等。也就是第二篇,模型樹優(yōu)化。 分析了 Vue 編譯三部曲的第一步,「如何將 template 編譯成 AST ?」上一篇已經(jīng)介紹,但我們還是來總結(jié)回顧下,parse 的目的是將開發(fā)者寫的 template 模板字符串轉(zhuǎn)換成抽象語法樹 AST ,AST 就這里...
閱讀 3220·2021-11-15 18:14
閱讀 1851·2021-09-22 10:51
閱讀 3364·2021-09-09 09:34
閱讀 3583·2021-09-06 15:02
閱讀 1119·2021-09-01 11:40
閱讀 3250·2019-08-30 13:58
閱讀 2583·2019-08-30 11:04
閱讀 1152·2019-08-28 18:31