成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

從現(xiàn)在起-徹底學(xué)會(huì) js ast

xiyang / 1620人閱讀

摘要:利用抽象語法樹可以對(duì)你的源代碼進(jìn)行修改優(yōu)化,甚至可以打造自己的編譯工具。


這是一棵樹嘛

直奔主題

抽象語法樹是js代碼另一種結(jié)構(gòu)映射,可以將js拆解成AST,也可以把AST轉(zhuǎn)成源代碼。這中間的過程就是我們的用武之地。 利用 抽象語法樹(AST) 可以對(duì)你的源代碼進(jìn)行修改、優(yōu)化,甚至可以打造自己的編譯工具。其實(shí)有點(diǎn)類似babel的功能。

AST高深的狠嚇人?

AST很簡(jiǎn)單,并沒有你想象的那樣高深。很多地方都把這個(gè)技術(shù)給夸大了,什么編譯原理,抽象語法樹 光看這名字就覺得嚇人。當(dāng)然一項(xiàng)技術(shù)總歸要起個(gè)名字,就像給自己的孩子取名字,肯定要起一個(gè)高大上,深有寓意的名字。所以,名字只是一個(gè)代號(hào)。從名字來看就會(huì)讓很多人望而卻步。但是ast超級(jí)簡(jiǎn)單,但是功能超級(jí)強(qiáng)大。

我們能用這個(gè)技術(shù)做很多有意思的東西,只要你能想到的。

本文術(shù)道結(jié)合,讓你感受到ast的有趣和簡(jiǎn)單,從此愛上ast,還能根據(jù)自己的需要打造自己的編譯器。

什么是AST?

ast全稱是abstract syntax tree,翻譯過來叫-抽象語法樹。其實(shí)這含兩個(gè)意思,一個(gè)是“抽象”,一個(gè)是“樹”。抽象表示把js代碼進(jìn)行了結(jié)構(gòu)化的轉(zhuǎn)化,轉(zhuǎn)化為一種數(shù)據(jù)結(jié)構(gòu)。這種數(shù)據(jù)結(jié)構(gòu)其實(shí)就是一個(gè)大的json對(duì)象,json我們都熟悉,他就像一顆枝繁葉茂的樹。

有樹根,有樹干,有樹枝,有樹葉.無論多小多大,都是一棵完整的樹。

如何生成AST?

你可以大致的想一下如果親自實(shí)現(xiàn)把js代碼轉(zhuǎn)換成結(jié)構(gòu)化的數(shù)據(jù)我們應(yīng)該怎么做?

有點(diǎn)像小時(shí)候拆解自己的玩具,每個(gè)零件之間都有著從屬關(guān)系。

對(duì)于如何生成ast,我們可能會(huì)想到分析js代碼的規(guī)則使用字符串處理、正則匹配等方法,如果對(duì)簡(jiǎn)單的代碼處理我們是可以實(shí)現(xiàn)的。但是如果能夠?qū)﹄S意的一段代碼進(jìn)行處理那就需要考慮非常多的情況。具體如何實(shí)現(xiàn)咱們不必過于糾結(jié),這也不是重點(diǎn)。

但最終的實(shí)現(xiàn)里我們能想到方法基本都會(huì)被用到。我們可以簡(jiǎn)化理解,也就是對(duì)js代碼經(jīng)過了一系列的加工處理,變成了一堆零件或者食材(像老媽給我們做的香噴噴的飯菜,但前提是先準(zhǔn)備好菜)。

這個(gè)拆解的過程可能較為復(fù)雜,所以我們需要用現(xiàn)成方法,直接拿過來用就可以了。

所以我們需要用到esprima、UglifyJS等庫,做菜的食材有很多種,所以會(huì)存在很多這樣的三方庫,而我們會(huì)使用其中一種就可以了。

先使用esprima 種菜,體會(huì)一下

種子:

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

成熟:

{
            "type": "FunctionDeclaration",//函數(shù)聲明
            "id": {
                "type": "Identifier",//標(biāo)識(shí)符
                "name": "fun" //函數(shù)名稱
            },
            "params": [//函數(shù)參數(shù)
                {
                    "type": "Identifier",//參數(shù)標(biāo)識(shí)符
                    "name": "a"http://參數(shù)名稱
                },
                {
                    "type": "Identifier",
                    "name": "b"
                }
            ],
            "body": {//函數(shù)體
                "type": "BlockStatement",//語句塊兒
                "body": []//具體內(nèi)容為空,因?yàn)槭强辗椒?            }
      }
有了AST能做什么?

到這一步你已經(jīng)可以把js代碼轉(zhuǎn)換成一棵結(jié)構(gòu)化的樹了,那下一步要做什么呢? 比如在沒有樹的情況下,你要對(duì)代碼里的某個(gè)代碼進(jìn)行替換。要把所有 console.log給注釋掉或者刪除,你可能會(huì)使用IDE的查找替換或者用node寫一個(gè)方法,讀取文件然后查找替換。

這種方式不夠安全也不夠科學(xué),稍有不慎就會(huì)把代碼給搞壞了。

但這個(gè)時(shí)候你有了結(jié)構(gòu)化代碼樹,是不是只要對(duì)這棵樹進(jìn)行修修剪剪然后把這棵樹轉(zhuǎn)換成為js代碼就可以了呢?

答案:肯定是可以的。因?yàn)闃湟呀?jīng)發(fā)生了變化,修改了樹就相當(dāng)于修改了源碼。

怎樣操作這棵樹呢?我想你應(yīng)該已經(jīng)知道了,就是對(duì)這json對(duì)象進(jìn)行操作,方法就多了去了,前提是你得有一點(diǎn)點(diǎn)js基礎(chǔ)。

又一個(gè)問題,怎樣把樹再轉(zhuǎn)成代碼?

腦洞打開,用遞歸加字符串拼接,這個(gè)方法應(yīng)該是可以的。

但是這棵樹不是你生成的,結(jié)構(gòu)特點(diǎn)你并不清楚,成千上萬個(gè)節(jié)點(diǎn)呢?怎么拼接?真要干,那可能得搞得流鼻血。

這就像是食材準(zhǔn)備好了,轉(zhuǎn)換成源碼的過程就是炒菜的過程。具體的轉(zhuǎn)源碼的原理不多說,也不必糾結(jié)。使用現(xiàn)成的方法就可以,所以要用到estraverse,escodegen這兩個(gè)庫。

estraverse 可以遍歷樹的所有節(jié)點(diǎn),省去你對(duì)樹的遞歸遍歷

escodegen 可以把樹再加工轉(zhuǎn)成源代碼

過程總結(jié)

到這里始終都沒有提到任何代碼,只是理論了一番,但是相信你已經(jīng)理解了ast以及ast的作用。然后在述說過程中引出了3個(gè)庫,有了這三個(gè)庫就可以對(duì)你的js代碼進(jìn)行多樣化處理,只要你能想到的。

看圖理解整個(gè)處理過程:

這個(gè)過程簡(jiǎn)單,清晰,所以說ast簡(jiǎn)單、有趣、好玩。因?yàn)榇丝檀a可以被你任意的蹂躪了。

實(shí)例應(yīng)用

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

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

把 == 改為全等 ===

把parsetInt不標(biāo)準(zhǔn)的調(diào)用改為標(biāo)準(zhǔn)用法 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 你已經(jīng)成年");
    }
}

轉(zhuǎn)成ast,由于轉(zhuǎn)成樹后結(jié)構(gòu)非常大,所以這里我只貼了一部分,你也可以到工具頁面自己生成下。

{
    "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看上去結(jié)構(gòu)復(fù)雜,盯著仔細(xì)看后基本都能看懂。所有的代碼都在特定的節(jié)點(diǎn)里面。具體的這里就不介紹了,可以到上面的工具地址去觀察不同的ast結(jié)構(gòu)。總之這就是一個(gè)對(duì)象,只要你能對(duì)這個(gè)對(duì)象進(jìn)行修改、添加、刪除即可。

開始實(shí)現(xiàn)以上功能
init

//引入工具包
const esprima = require("esprima");//JS語法樹模塊
const estraverse = require("estraverse");//JS語法樹遍歷各節(jié)點(diǎn)
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.把 == 改為全等 ===

/**
 * 設(shè)置全等
 */
function toEqual(node) {
    if (node.operator === "==") {
        node.operator = "===";
    }
}

把parseInt改成標(biāo)準(zhǔn)調(diào)用

/**
 * 把parseint改為標(biāo)準(zhǔn)方法
 * @param {節(jié)點(diǎn)} node 
 */
function setParseint(node) {
    //判斷節(jié)點(diǎn)類型 方法名稱,方法的參數(shù)的數(shù)量,數(shù)量為1就增加第二個(gè)參數(shù)
    if (node.type === "CallExpression" && node.callee.name === "parseInt" && node.arguments.length===1){

        node.arguments.push({//增加參數(shù),其實(shí)就是數(shù)組操作
            "type": "Literal",
            "value": 10,
            "raw": "10"
        });
    }
}

//生成目標(biāo)代碼
const code = escodegen.generate(ast);
//寫入文件.....
//....你懂的

代碼不多,需求簡(jiǎn)單,但已足夠能說明整個(gè)處理過程以及ast的強(qiáng)大。 ast的節(jié)點(diǎn)很多,有些凌亂,送你一首歌【汪峰的無所謂】,操作的時(shí)候只要關(guān)心你自己的需求就可以,不需要對(duì)所有的節(jié)點(diǎn)都搞明白。按需處理就可以。

AST技術(shù)的應(yīng)用

雖然平時(shí)用不到ast,但又時(shí)刻都在使用ast技術(shù)。家喻戶曉、無人不知的babel,webpack,還有jd taro等都把a(bǔ)st用的淋漓盡致,脫離了ast他們就跪了。

AST這么簡(jiǎn)單,好沒技術(shù)含量
AST沒有技術(shù)含量嗎?怎么可能呢,如果真這么認(rèn)為怕是會(huì)被笑掉大牙的。如果僅僅停留在使用層面的話,理解到這步已經(jīng)基本可以了,只要是你能對(duì)這棵樹做修剪就可以對(duì)源代碼做手腳。

另外ast怎樣生成的?怎樣把a(bǔ)st轉(zhuǎn)換成源碼的?這就有點(diǎn)高深了。會(huì)使用就像是在山腳下能看到的風(fēng)景有限,理解了背后原理機(jī)制就像是爬上了山頂,別樣的風(fēng)景盡收眼底。不過上不上山看個(gè)人興趣,有興趣的同學(xué)可以去看源碼、做研究,這里就不再多說,因?yàn)槲乙膊恢?。哈哈?/p> 總結(jié)

本文主要介紹了

什么是ast:

ast其實(shí)就把js代碼進(jìn)行抽象為一種json結(jié)構(gòu);

ast的用途:

利用ast可以方便的優(yōu)化和修改代碼,還能打造自己的編譯器;

然后通過具體的示例演示了怎樣操作ast,最終是希望你能對(duì)ast有一個(gè)系統(tǒng)全局的認(rèn)識(shí)和理解并能夠利用ast打造自己的編譯工具。

演示代碼下載,歡迎star

https://github.com/bigerfe/fo...

自家觀點(diǎn),歡迎打臉

原創(chuàng)不易,請(qǐng)多鼓勵(lì)

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/101339.html

相關(guān)文章

  • 平庸前端碼農(nóng)之蛻變 — AST

    摘要:為什么要談抽象語法樹如果你查看目前任何主流的項(xiàng)目中的,會(huì)發(fā)現(xiàn)前些年的不計(jì)其數(shù)的插件誕生。什么是抽象語法樹估計(jì)很多同學(xué)會(huì)和圖中的喵一樣,看完這段官方的定義一臉懵逼。它讀取我們的代碼,然后把它們按照預(yù)定的規(guī)則合并成一個(gè)個(gè)的標(biāo)識(shí)。 前言 首先,先說明下該文章是譯文,原文出自《AST for JavaScript developers》。很少花時(shí)間特地翻譯一篇文章,咬文嚼字是件很累的事情,實(shí)在...

    dreamans 評(píng)論0 收藏0
  • 【譯】小二百行 JavaScript 打造 lambda 演算解釋器

    摘要:在開始解析之前,先通過詞法分析器運(yùn)行源碼,這會(huì)將源碼打散成語法中全大寫的部分。我們基于每個(gè)規(guī)則的名稱的左側(cè)為其創(chuàng)建一個(gè)方法,再來看右側(cè)內(nèi)容如果是全大寫的單詞,說明它是一個(gè)終止符即一個(gè),詞法分析器會(huì)用到它。 本文轉(zhuǎn)載自:眾成翻譯譯者:文藺鏈接:http://www.zcfy.cc/article/661原文:http://tadeuzagallo.com/blog/writing-a-l...

    KitorinZero 評(píng)論0 收藏0
  • babel的初步了解

    摘要:表示的是在嚴(yán)格模式下解析并且允許模塊定義即能識(shí)別和語法識(shí)別不了。 前段時(shí)間開始研究ast,然后慢慢的順便把babel都研究了,至于ast稍后的時(shí)間會(huì)寫一篇介紹性博客專門介紹ast,本博客先介紹一下babel的基本知識(shí)點(diǎn)。 背景: 由于現(xiàn)在前端出現(xiàn)了很多非es5的語法,如jsx,.vue,ts等等的格式和寫法,如果要在瀏覽器的設(shè)備上識(shí)別并執(zhí)行,需要額外將這些非傳統(tǒng)格式的語法轉(zhuǎn)成傳統(tǒng)的es...

    _Dreams 評(píng)論0 收藏0
  • 徹底搞懂 PHP 變量結(jié)構(gòu)體,多數(shù)文章觀點(diǎn)不準(zhǔn)確

    摘要:中的用于類型整型和資源類型用于浮點(diǎn)類型用于字符串用于數(shù)組用于對(duì)象用于常量表達(dá)式才有多數(shù)文章,在提到變量結(jié)構(gòu)體的時(shí)候,都提到,實(shí)際上這個(gè)論述并不準(zhǔn)確,在為時(shí),這個(gè)結(jié)果是正確的。主要看中的,是兩個(gè),這個(gè)永遠(yuǎn)是個(gè)字節(jié),所以,因此。 PHP5 中的 zval // 1. zval typedef struct _zval_struct { zvalue_value value; ...

    mtunique 評(píng)論0 收藏0
  • 阿里云前端周刊 - 第 31 期

    摘要:發(fā)布按照官方發(fā)布計(jì)劃,的發(fā)布意味著進(jìn)入階段,徹底退出舞臺(tái),的還有半年結(jié)束。為了應(yīng)對(duì)這個(gè)挑戰(zhàn),美團(tuán)點(diǎn)評(píng)境外度假前端研發(fā)團(tuán)隊(duì)自年月起啟動(dòng)了面向端用戶的赫爾墨斯項(xiàng)目。前端技術(shù)越來越復(fù)雜,有不低的技術(shù)門檻。 推薦 1. 利用 Dawn 工程化工具實(shí)踐 MobX 數(shù)據(jù)流管理方案 https://zhuanlan.zhihu.com/p/... 項(xiàng)目在最初應(yīng)用 MobX 時(shí),對(duì)較為復(fù)雜的多人協(xié)作項(xiàng)...

    madthumb 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

xiyang

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<