摘要:利用抽象語法樹可以對你的源代碼進行修改優化,甚至可以打造自己的編譯工具。
這是一棵樹嘛
抽象語法樹是js代碼另一種結構映射,可以將js拆解成AST,也可以把AST轉成源代碼。這中間的過程就是我們的用武之地。 利用 抽象語法樹(AST) 可以對你的源代碼進行修改、優化,甚至可以打造自己的編譯工具。其實有點類似babel的功能。
AST高深的狠嚇人?AST很簡單,并沒有你想象的那樣高深。很多地方都把這個技術給夸大了,什么編譯原理,抽象語法樹 光看這名字就覺得嚇人。當然一項技術總歸要起個名字,就像給自己的孩子取名字,肯定要起一個高大上,深有寓意的名字。所以,名字只是一個代號。從名字來看就會讓很多人望而卻步。但是ast超級簡單,但是功能超級強大。
我們能用這個技術做很多有意思的東西,只要你能想到的。
本文術道結合,讓你感受到ast的有趣和簡單,從此愛上ast,還能根據自己的需要打造自己的編譯器。
什么是AST?ast全稱是abstract syntax tree,翻譯過來叫-抽象語法樹。其實這含兩個意思,一個是“抽象”,一個是“樹”。抽象表示把js代碼進行了結構化的轉化,轉化為一種數據結構。這種數據結構其實就是一個大的json對象,json我們都熟悉,他就像一顆枝繁葉茂的樹。
有樹根,有樹干,有樹枝,有樹葉.無論多小多大,都是一棵完整的樹。
如何生成AST?你可以大致的想一下如果親自實現把js代碼轉換成結構化的數據我們應該怎么做?
有點像小時候拆解自己的玩具,每個零件之間都有著從屬關系。
對于如何生成ast,我們可能會想到分析js代碼的規則使用字符串處理、正則匹配等方法,如果對簡單的代碼處理我們是可以實現的。但是如果能夠對隨意的一段代碼進行處理那就需要考慮非常多的情況。具體如何實現咱們不必過于糾結,這也不是重點。
但最終的實現里我們能想到方法基本都會被用到。我們可以簡化理解,也就是對js代碼經過了一系列的加工處理,變成了一堆零件或者食材(像老媽給我們做的香噴噴的飯菜,但前提是先準備好菜)。
這個拆解的過程可能較為復雜,所以我們需要用現成方法,直接拿過來用就可以了。
所以我們需要用到esprima、UglifyJS等庫,做菜的食材有很多種,所以會存在很多這樣的三方庫,而我們會使用其中一種就可以了。
先使用esprima 種菜,體會一下
種子:
//源代碼 function fun(a,b){ }
成熟:
{ "type": "FunctionDeclaration",//函數聲明 "id": { "type": "Identifier",//標識符 "name": "fun" //函數名稱 }, "params": [//函數參數 { "type": "Identifier",//參數標識符 "name": "a"http://參數名稱 }, { "type": "Identifier", "name": "b" } ], "body": {//函數體 "type": "BlockStatement",//語句塊兒 "body": []//具體內容為空,因為是空方法 } }有了AST能做什么?
到這一步你已經可以把js代碼轉換成一棵結構化的樹了,那下一步要做什么呢? 比如在沒有樹的情況下,你要對代碼里的某個代碼進行替換。要把所有 console.log給注釋掉或者刪除,你可能會使用IDE的查找替換或者用node寫一個方法,讀取文件然后查找替換。
這種方式不夠安全也不夠科學,稍有不慎就會把代碼給搞壞了。
但這個時候你有了結構化代碼樹,是不是只要對這棵樹進行修修剪剪然后把這棵樹轉換成為js代碼就可以了呢?
答案:肯定是可以的。因為樹已經發生了變化,修改了樹就相當于修改了源碼。
怎樣操作這棵樹呢?我想你應該已經知道了,就是對這json對象進行操作,方法就多了去了,前提是你得有一點點js基礎。
又一個問題,怎樣把樹再轉成代碼?腦洞打開,用遞歸加字符串拼接,這個方法應該是可以的。
但是這棵樹不是你生成的,結構特點你并不清楚,成千上萬個節點呢?怎么拼接?真要干,那可能得搞得流鼻血。
這就像是食材準備好了,轉換成源碼的過程就是炒菜的過程。具體的轉源碼的原理不多說,也不必糾結。使用現成的方法就可以,所以要用到estraverse,escodegen這兩個庫。
estraverse 可以遍歷樹的所有節點,省去你對樹的遞歸遍歷
escodegen 可以把樹再加工轉成源代碼
過程總結到這里始終都沒有提到任何代碼,只是理論了一番,但是相信你已經理解了ast以及ast的作用。然后在述說過程中引出了3個庫,有了這三個庫就可以對你的js代碼進行多樣化處理,只要你能想到的。
看圖理解整個處理過程:
這個過程簡單,清晰,所以說ast簡單、有趣、好玩。因為此刻代碼可以被你任意的蹂躪了。
實例應用說的再清楚都不夠直觀,畢竟都是腦補,不如看代碼來的爽快。
這里就拿日常編碼中的一些小問題舉例,來演示一下AST的使用。
把 == 改為全等 ===
把parsetInt不標準的調用改為標準用法 parseInt(a)-> parseInt(a,10)
這里我使用esprima的官方工具生成了ast,工具地址http://esprima.org/demo/parse...
看下要處理的源碼:
//源碼 function fun1() { console.log("fun1"); } function fun2(opt) { if (opt.status == 1) { console.log("1"); } if (opt.status == 2) { console.log("2"); } } function fun3(age) { if (parseInt(age) >= 18) { console.log("ok 你已經成年"); } }
轉成ast,由于轉成樹后結構非常大,所以這里我只貼了一部分,你也可以到工具頁面自己生成下。
{ "type": "Program", "body": [ { "type": "FunctionDeclaration", "id": { "type": "Identifier", "name": "fun1" }, "params": [], "body": { "type": "BlockStatement", "body": [ { "type": "ExpressionStatement", "expression": { "type": "CallExpression", "callee": { "type": "MemberExpression", "computed": false, "object": { "type": "Identifier", "name": "console" }, "property": { "type": "Identifier", "name": "log" } }, "arguments": [ { "type": "Literal", "value": "fun1", "raw": ""fun1"" } ] } } ] }, "generator": false, "expression": false, "async": false } ] }
ast看上去結構復雜,盯著仔細看后基本都能看懂。所有的代碼都在特定的節點里面。具體的這里就不介紹了,可以到上面的工具地址去觀察不同的ast結構。總之這就是一個對象,只要你能對這個對象進行修改、添加、刪除即可。
開始實現以上功能
init
//引入工具包 const esprima = require("esprima");//JS語法樹模塊 const estraverse = require("estraverse");//JS語法樹遍歷各節點 const escodegen = require("escodegen");//JS語法樹反編譯模塊 //獲?取代碼ast const AST = esprima.parseScript(jsCode); /** * * @param {遍歷語法樹} ast */ function walkIn(ast){ estraverse.traverse(ast, { enter: (node) => { toEqual(node);//把 == 改為全等 === setParseint(node); //parseInt(a)-> parseInt(a,10) } }); }
2.把 == 改為全等 ===
/** * 設置全等 */ function toEqual(node) { if (node.operator === "==") { node.operator = "==="; } }
把parseInt改成標準調用
/** * 把parseint改為標準方法 * @param {節點} node */ 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" }); } } //生成目標代碼 const code = escodegen.generate(ast); //寫入文件..... //....你懂的
代碼不多,需求簡單,但已足夠能說明整個處理過程以及ast的強大。 ast的節點很多,有些凌亂,送你一首歌【汪峰的無所謂】,操作的時候只要關心你自己的需求就可以,不需要對所有的節點都搞明白。按需處理就可以。
AST技術的應用雖然平時用不到ast,但又時刻都在使用ast技術。家喻戶曉、無人不知的babel,webpack,還有jd taro等都把ast用的淋漓盡致,脫離了ast他們就跪了。
AST這么簡單,好沒技術含量
AST沒有技術含量嗎?怎么可能呢,如果真這么認為怕是會被笑掉大牙的。如果僅僅停留在使用層面的話,理解到這步已經基本可以了,只要是你能對這棵樹做修剪就可以對源代碼做手腳。
另外ast怎樣生成的?怎樣把ast轉換成源碼的?這就有點高深了。會使用就像是在山腳下能看到的風景有限,理解了背后原理機制就像是爬上了山頂,別樣的風景盡收眼底。不過上不上山看個人興趣,有興趣的同學可以去看源碼、做研究,這里就不再多說,因為我也不知道。哈哈哈
總結本文主要介紹了
什么是ast:
ast其實就把js代碼進行抽象為一種json結構;
ast的用途:
利用ast可以方便的優化和修改代碼,還能打造自己的編譯器;
然后通過具體的示例演示了怎樣操作ast,最終是希望你能對ast有一個系統全局的認識和理解并能夠利用ast打造自己的編譯工具。
演示代碼下載,歡迎star
https://github.com/bigerfe/fo...
自家觀點,歡迎打臉
原創不易,請多鼓勵
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101339.html
摘要:為什么要談抽象語法樹如果你查看目前任何主流的項目中的,會發現前些年的不計其數的插件誕生。什么是抽象語法樹估計很多同學會和圖中的喵一樣,看完這段官方的定義一臉懵逼。它讀取我們的代碼,然后把它們按照預定的規則合并成一個個的標識。 前言 首先,先說明下該文章是譯文,原文出自《AST for JavaScript developers》。很少花時間特地翻譯一篇文章,咬文嚼字是件很累的事情,實在...
摘要:在開始解析之前,先通過詞法分析器運行源碼,這會將源碼打散成語法中全大寫的部分。我們基于每個規則的名稱的左側為其創建一個方法,再來看右側內容如果是全大寫的單詞,說明它是一個終止符即一個,詞法分析器會用到它。 本文轉載自:眾成翻譯譯者:文藺鏈接:http://www.zcfy.cc/article/661原文:http://tadeuzagallo.com/blog/writing-a-l...
摘要:表示的是在嚴格模式下解析并且允許模塊定義即能識別和語法識別不了。 前段時間開始研究ast,然后慢慢的順便把babel都研究了,至于ast稍后的時間會寫一篇介紹性博客專門介紹ast,本博客先介紹一下babel的基本知識點。 背景: 由于現在前端出現了很多非es5的語法,如jsx,.vue,ts等等的格式和寫法,如果要在瀏覽器的設備上識別并執行,需要額外將這些非傳統格式的語法轉成傳統的es...
摘要:中的用于類型整型和資源類型用于浮點類型用于字符串用于數組用于對象用于常量表達式才有多數文章,在提到變量結構體的時候,都提到,實際上這個論述并不準確,在為時,這個結果是正確的。主要看中的,是兩個,這個永遠是個字節,所以,因此。 PHP5 中的 zval // 1. zval typedef struct _zval_struct { zvalue_value value; ...
摘要:發布按照官方發布計劃,的發布意味著進入階段,徹底退出舞臺,的還有半年結束。為了應對這個挑戰,美團點評境外度假前端研發團隊自年月起啟動了面向端用戶的赫爾墨斯項目。前端技術越來越復雜,有不低的技術門檻。 推薦 1. 利用 Dawn 工程化工具實踐 MobX 數據流管理方案 https://zhuanlan.zhihu.com/p/... 項目在最初應用 MobX 時,對較為復雜的多人協作項...
閱讀 2287·2023-04-25 14:22
閱讀 3733·2021-11-15 18:12
閱讀 1293·2019-08-30 15:44
閱讀 3214·2019-08-29 15:37
閱讀 637·2019-08-29 13:49
閱讀 3454·2019-08-26 12:11
閱讀 865·2019-08-23 18:28
閱讀 1581·2019-08-23 14:55