摘要:本文除了介紹的一些基本概念外,更偏重實戰(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