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

資訊專欄INFORMATION COLUMN

AST 實(shí)戰(zhàn)

asoren / 3128人閱讀

摘要:本文除了介紹的一些基本概念外,更偏重實(shí)戰(zhàn),講解如何利用它來對(duì)代碼進(jìn)行修改。二基本概念全稱,也就是抽象語(yǔ)法樹,它是將編程語(yǔ)言轉(zhuǎn)換成機(jī)器語(yǔ)言的橋梁。四實(shí)戰(zhàn)下面我們來詳細(xì)看看如何對(duì)進(jìn)行操作。

歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:

一、前言

最近突然對(duì) AST 產(chǎn)生了興趣,深入了解后發(fā)現(xiàn)它的使用場(chǎng)景還真的不少,很多我們?nèi)粘i_發(fā)使用的工具都跟它息息相關(guān),如 Babel、ESLint 和 Prettier 等。本文除了介紹 AST 的一些基本概念外,更偏重實(shí)戰(zhàn),講解如何利用它來對(duì)代碼進(jìn)行修改。

二、基本概念

AST 全稱 Abstract Syntax Tree,也就是抽象語(yǔ)法樹,它是將編程語(yǔ)言轉(zhuǎn)換成機(jī)器語(yǔ)言的橋梁。瀏覽器在解析 JS 的過程中,會(huì)根據(jù) ECMAScript 標(biāo)準(zhǔn)將字符串進(jìn)行分詞,拆分為一個(gè)個(gè)語(yǔ)法單元。然后再遍歷這些語(yǔ)法單元,進(jìn)行語(yǔ)義分析,構(gòu)造出 AST。最后再使用 JIT 編譯器的全代碼生成器,將 AST 轉(zhuǎn)換為本地可執(zhí)行的機(jī)器碼。如下面一段代碼:

function add(a, b) {
    return a + b;
}

進(jìn)行分詞后,會(huì)得到這些 token:

對(duì) token 進(jìn)行分析,最終會(huì)得到這樣一棵 AST(簡(jiǎn)化版):

{
  "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)換為機(jī)器碼了,在此不再贅述。

三、Babel 工作原理

AST 除了可以轉(zhuǎn)換為機(jī)器碼外,還能做很多事情,如 Babel 就能通過分析 AST,將 ES6 的代碼轉(zhuǎn)換成 ES5。

Babel 的編譯過程分為 3 個(gè)階段:

解析:將代碼字符串解析成抽象語(yǔ)法樹

變換:對(duì)抽象語(yǔ)法樹進(jìn)行變換操作

生成:根據(jù)變換后的抽象語(yǔ)法樹生成新的代碼字符串

Babel 實(shí)現(xiàn)了一個(gè) JS 版本的解析器Babel parser,它能將 JS 字符串轉(zhuǎn)換為 JSON 結(jié)構(gòu)的 AST。為了方便對(duì)這棵樹進(jìn)行遍歷和變換操作,babel 又提供了traverse工具函數(shù)。完成 AST 的修改后,可以使用generator生成新的代碼。

四、AST 實(shí)戰(zhàn)

下面我們來詳細(xì)看看如何對(duì) AST 進(jìn)行操作。先建好如下的代碼模板:

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)造一個(gè) 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)造這個(gè)ExpressionStatement:

const code = ``;
const ast = parser.parse(code);

// 生成 literal
const literal = t.stringLiteral("hello world")
// 生成 expressionStatement
const exp = t.expressionStatement(literal)  
// 將表達(dá)式放入body中
ast.program.body.push(exp)

const output = generate(ast, {}, code);

可以看到 AST 的創(chuàng)建過程就是自底向上創(chuàng)建各種節(jié)點(diǎn)的過程。這里我們借助 babel 提供的types對(duì)象幫我們創(chuàng)建各種類型的節(jié)點(diǎn)。更多類型可以查閱這里。

同樣道理,下面我們來看看如何構(gòu)造一個(gè)賦值語(yǔ)句:

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])

// 將表達(dá)式放入body中
ast.program.body.push(declaration)

const output = generate(ast, {}, code);

獲取 AST 中的節(jié)點(diǎn)

下面我們將對(duì)這段代碼進(jìn)行操作:

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方法的第二個(gè)參數(shù)是一個(gè)對(duì)象,只要提供與節(jié)點(diǎn)類型同名的屬性,就能獲取到所有的這種類型的節(jié)點(diǎn)。通過path參數(shù)能訪問到節(jié)點(diǎn)信息,進(jìn)而找出需要操作的節(jié)點(diǎn)。上面的代碼中,我們找到方法名為data的方法后,將其改名為myData,然后停止遍歷,生成新的代碼。

替換 AST 中的節(jié)點(diǎn)

可以使用replaceWithreplaceWithSourceString替換節(jié)點(diǎn),例子如下:

// 將 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"))
        );
        
      // 下面的操作跟上一條語(yǔ)句等價(jià),更加直觀方便
      // path.get("object").replaceWithSourceString("this.data");
    }
  }
});

const output = generate(ast, {}, code);

插入新的節(jié)點(diǎn)

可以使用pushContainerinsertBeforeinsertAfter等方法來插入節(jié)點(diǎn):

// 這個(gè)例子示范了 3 種節(jié)點(diǎn)插入的方法

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é)點(diǎn)

使用remove方法來刪除節(jié)點(diǎn):

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,對(duì) AST 進(jìn)行增刪改查的操作。?掌握這項(xiàng)技能,再加上一點(diǎn)想象力,就能制作出實(shí)用的代碼分析和轉(zhuǎn)換工具。

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

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

相關(guān)文章

  • Babylon-AST初探-實(shí)戰(zhàn)

    摘要:生成屬性這一步,我們要先提取原函數(shù)中的的對(duì)象。所以這里我們還是主要使用來訪問節(jié)點(diǎn)獲取第一級(jí)的,也就是函數(shù)體將合并的寫法用生成生成生成插入到原函數(shù)下方刪除原函數(shù)程序輸出將中的屬性提升一級(jí)這里遍歷中的屬性沒有再采用,因?yàn)檫@里結(jié)構(gòu)是固定的。 ??經(jīng)過之前的三篇文章介紹,AST的CRUD都已經(jīng)完成。下面主要通過vue轉(zhuǎn)小程序過程中需要用到的部分關(guān)鍵技術(shù)來實(shí)戰(zhàn)。 下面的例子的核心代碼依然是最簡(jiǎn)單...

    godiscoder 評(píng)論0 收藏0
  • Babylon-AST初探-代碼更新&刪除(Update & Remove)

    摘要:操作通常配合來完成。因?yàn)槭莻€(gè)數(shù)組,因此,我們可以直接使用數(shù)組操作自我毀滅方法極為簡(jiǎn)單,找到要?jiǎng)h除的,執(zhí)行就結(jié)束了。如上述代碼,我們要?jiǎng)h除屬性,代碼如下到目前為止,的我們都介紹完了,下面一篇文章以轉(zhuǎn)小程序?yàn)槔?,我們來?shí)戰(zhàn)一波。 ??通過前兩篇文章的介紹,大家已經(jīng)了解了Create和Retrieve,我們接著介紹Update和 Remove操作。Update操作通常配合Create來完成。...

    levius 評(píng)論0 收藏0
  • AST抽象語(yǔ)法樹——最基礎(chǔ)的javascript重點(diǎn)知識(shí),99%的人根本不了解

    摘要:抽象語(yǔ)法樹,是一個(gè)非常基礎(chǔ)而重要的知識(shí)點(diǎn),但國(guó)內(nèi)的文檔卻幾乎一片空白。事實(shí)上,在世界中,你可以認(rèn)為抽象語(yǔ)法樹是最底層。通過抽象語(yǔ)法樹解析,我們可以像童年時(shí)拆解玩具一樣,透視這臺(tái)機(jī)器的運(yùn)轉(zhuǎn),并且重新按著你的意愿來組裝。 抽象語(yǔ)法樹(AST),是一個(gè)非?;A(chǔ)而重要的知識(shí)點(diǎn),但國(guó)內(nèi)的文檔卻幾乎一片空白。本文將帶大家從底層了解AST,并且通過發(fā)布一個(gè)小型前端工具,來帶大家了解AST的強(qiáng)大功能 ...

    godiscoder 評(píng)論0 收藏0
  • FE.SRC-Vue實(shí)戰(zhàn)與原理筆記

    摘要:超出此時(shí)間則渲染錯(cuò)誤組件。元素節(jié)點(diǎn)總共有種類型,為表示是普通元素,為表示是表達(dá)式,為表示是純文本。 實(shí)戰(zhàn) - 插件 form-validate {{ error }} ...

    wangjuntytl 評(píng)論0 收藏0
  • Vue編程三部曲之模型樹優(yōu)化實(shí)戰(zhàn)代碼

      實(shí)踐是所有展示最好的方法,因此我覺得可以不必十分細(xì)致的,但我們的展示卻是整體的流程、輸入和輸出?,F(xiàn)在我們就看看Vue 的指令、內(nèi)置組件等。也就是第二篇,模型樹優(yōu)化?! 》治隽?Vue 編譯三部曲的第一步,「如何將 template 編譯成 AST ?」上一篇已經(jīng)介紹,但我們還是來總結(jié)回顧下,parse 的目的是將開發(fā)者寫的 template 模板字符串轉(zhuǎn)換成抽象語(yǔ)法樹 AST ,AST 就這里...

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

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

0條評(píng)論

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