從如今起-完全學會 js ast

0afc5d1784e3414ad8aa93c6b74929bf.png
這是一棵樹嘛javascript

直奔主題

抽象語法樹是js代碼另外一種結構映射,能夠將js拆解成AST,也能夠把AST轉成源代碼。這中間的過程就是咱們的用武之地。 利用 抽象語法樹(AST) 能夠對你的源代碼進行修改、優化,甚至能夠打造本身的編譯工具。其實有點相似babel的功能。html

AST高深的狠嚇人?

AST很簡單,並無你想象的那樣高深。不少地方都把這個技術給誇大了,什麼編譯原理,抽象語法樹 光看這名字就以爲嚇人。固然一項技術總歸要起個名字,就像給本身的孩子取名字,確定要起一個高大上,深有寓意的名字。因此,名字只是一個代號。從名字來看就會讓不少人望而卻步。可是ast超級簡單,可是功能超級強大。java

咱們能用這個技術作不少有意思的東西,只要你能想到的。node

本文術道結合,讓你感覺到ast的有趣和簡單,今後愛上ast,還能根據本身的須要打造本身的編譯器。webpack

什麼是AST?

ast全稱是abstract syntax tree,翻譯過來叫-抽象語法樹。其實這含兩個意思,一個是「抽象」,一個是「樹」。抽象表示把js代碼進行告終構化的轉化,轉化爲一種數據結構。這種數據結構其實就是一個大的json對象,json咱們都熟悉,他就像一顆枝繁葉茂的樹。git

有樹根,有樹幹,有樹枝,有樹葉.不管多小多大,都是一棵完整的樹。github

如何生成AST?

你能夠大體的想一下若是親自實現把js代碼轉換成結構化的數據咱們應該怎麼作?web

有點像小時候拆解本身的玩具,每一個零件之間都有着從屬關係。express

對於如何生成ast,咱們可能會想到分析js代碼的規則使用字符串處理、正則匹配等方法,若是對簡單的代碼處理咱們是能夠實現的。可是若是可以對隨意的一段代碼進行處理那就須要考慮很是多的狀況。具體如何實現我們沒必要過於糾結,這也不是重點。json

但最終的實現裏咱們能想到方法基本都會被用到。咱們能夠簡化理解,也就是對js代碼通過了一系列的加工處理,變成了一堆零件或者食材(像老媽給咱們作的香噴噴的飯菜,但前提是先準備好菜)。

這個拆解的過程可能較爲複雜,因此咱們須要用現成方法,直接拿過來用就能夠了。

因此咱們須要用到esprima、UglifyJS等庫,作菜的食材有不少種,因此會存在不少這樣的三方庫,而咱們會使用其中一種就能夠了。

先使用esprima 種菜,體會一下

種子:

//源代碼
function fun(a,b){
  
}

成熟:

{
            "type": "FunctionDeclaration",//函數聲明
            "id": {
                "type": "Identifier",//標識符
                "name": "fun" //函數名稱
            },
            "params": [//函數參數
                {
                    "type": "Identifier",//參數標識符
                    "name": "a"//參數名稱
                },
                {
                    "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代碼進行多樣化處理,只要你能想到的。

看圖理解整個處理過程:

4226719341e4d0193ae3882e3dfc30e6.png@wm_2,t_55m+5a625Y+3L+WJjeerr+efpemBkw==,fc_ffffff,ff_U2ltSGVp,sz_20,x_13,y_13

這個過程簡單,清晰,因此說ast簡單、有趣、好玩。由於此刻代碼能夠被你任意的蹂躪了。

實例應用

說的再清楚都不夠直觀,畢竟都是腦補,不如看代碼來的爽快。

這裏就拿平常編碼中的一些小問題舉例,來演示一下AST的使用。

  1. 把 == 改成全等 ===
  2. 把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 = '===';
    }
}
  1. 把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...

自家觀點,歡迎打臉

原創不易,請多鼓勵

相關文章
相關標籤/搜索