01
抽象語法樹
使用正則表達式,只能把JavaScript源代碼當做文本來處理,能力很弱,無法觸及到JavaScript的語法層面,正則表達式沒法知道這個地方是變量,那個地方是函數名.....
如果能把JavaScript源碼轉化成結構化的對象,就可以精確地知道一段代碼中有哪些變量名,函數名,參數...... 這樣就可以寫程序就可以進行處理了。
張大胖想起來自己沒有考及格的《編譯原理》,裡邊講到了抽象語法樹(AST)不就是所謂結構化的東西嗎?
比如表達式 result = 6+7*3 , 用抽象語法樹來表示就是:
如果把所有的JavaScript代碼都轉化成這樣一顆AST的樹,那代碼的一切都盡在掌握, 可以任意修改了。
但是這其中有三個問題:
1. 怎麼從文本形式的源代碼形成這麼一個AST ?
讓自己寫程序實現那就太難了,得做詞法分析,語法分析等等。
2. 如何遍歷這個AST,來修改這顆樹的枝枝葉葉?
比如我想在AST這棵樹中添加一個新的節點,該怎麼做?
3. 修改完成以後,怎麼再次把AST變成文本的源代碼?
張大胖趕緊打開Google 搜索,很快便找到了三個開源的工具,正好完成對應的三個功能:
estraverse:遍歷樹的節點並修改
escodegen : 把修改完的AST再次轉化為源代碼。
02
創建AST
說幹就幹,張大胖準備了一段代碼來做實驗:
//源碼
function fun1(opt) {
if (opt.status == 1) {
console.log('1');
}
if (opt.status == 2) {
console.log('2');
}
}
function fun2(age) {
if (parseInt(age) >= 18) {
console.log('ok 你已經成年');
}
}
使用esprima,輕輕鬆鬆就把它轉化成了抽象語法樹。
//JS語法樹模塊
const esprima = require('esprima');
//創建AST
const AST = esprima.parseScript(jsCode);
(由於轉成樹後結構非常大,這裡不再展示了, 感興趣的同學自己可以到http://esprima.org/demo/parse.html 去玩一把, 很有趣。 )
比如: if (parseInt(age) >= 18) 這一句,就被轉化成了這樣:
03
遍歷修改AST
有了AST,就可以就是遍歷和修改了,還是使用開源的工具。
//JS語法樹遍歷各節點
const estraverse = require('estraverse');
//從JS語法樹生成源代碼
const escodegen = require('escodegen');
function walkIn(ast){
estraverse.traverse(ast, {
enter: (node) => {
toEqual(node);//把 == 改為全等 ===
setParseInt(node); //parseInt(a)-> parseInt(a,10)
}
});
}
這個函數負責把‘==’改成‘===’
function toEqual(node) {
if (node.operator === '==') {
node.operator = '===';
}
}
這個函數負責把parseInt改成標準調用:
function setParseInt(node) {
//判斷節點類型 方法名稱,方法的參數的數量,數量為1就增加第二個參數。
if (node.type === 'CallExpression' && node.callee.name === 'parseInt' && node.arguments.length===1){
node.arguments.push({//增加參數,其實就是數組操作
"type": "Literal",
"value": 10,
"raw": "10"
});
}
}
經過這個函數,原來的 if (parseInt(age) >= 18) 就變成了下圖這樣,相當於增加了一個節點,對應的代碼就是 :if (parseInt(age,10) >= 18)
最後使用escodegen 把修改過的AST再次變成源代碼,就大功告成了:
//生成目標代碼
const code = escodegen.generate(ast);
//寫入文件.....
//....你懂的
通過這個實驗,張大胖基本上了解了AST的原理和用法,接下來可以著手正式的編程了。
04
總結
本文的例子用AST也許不是最優解, 主要是為了展示AST的處理技術, AST實際上就是源代碼的一種結構化表示, 利用它及相關工具可以方便地優化和修改代碼,只要是你能對這棵“AST樹”做“修剪”就可以對源代碼做各種“手腳”:
JavaScript代碼語法、風格的檢查
在IDE中的錯誤提示、自動補全,重構
代碼的壓縮和混淆 代碼的轉換 ......