欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關(guān)于前端要知道的?AST知識(shí)

 更新時(shí)間:2023年04月13日 09:53:51   作者:技術(shù)小張zz  
這篇文章主要介紹了關(guān)于前端要知道的?AST知識(shí),在計(jì)算機(jī)科學(xué)中,抽象語法樹是源代碼語法結(jié)構(gòu)的一種抽象表示,需要的朋友可以參考下

認(rèn)識(shí) AST

定義:在計(jì)算機(jī)科學(xué)中,抽象語法樹是源代碼語法結(jié)構(gòu)的一種抽象表示。它以樹狀的形式表現(xiàn)編程語言的語法結(jié)構(gòu),樹上的每個(gè)節(jié)點(diǎn)都表示源代碼中的一種結(jié)構(gòu)。之所以說語法是“抽象”的,是因?yàn)檫@里的語法并不會(huì)表示出真實(shí)語法中出現(xiàn)的每個(gè)細(xì)節(jié)。

從定義中我們只需要知道一件事就行,那就是 AST 是一種樹形結(jié)構(gòu),并且是某種代碼的一種抽象表示。

在線可視化網(wǎng)站:https://astexplorer.net/ ,利用這個(gè)網(wǎng)站我們可以很清晰的看到各種語言的 AST 結(jié)構(gòu)。

estree[1]

estree 就是 es 語法對(duì)應(yīng)的標(biāo)準(zhǔn) AST,作為一個(gè)前端也比較方便理解。我們以官方文檔為例

https://github.com/estree/estree/blob/master/es5.md

下面看一個(gè)代碼

console.log('1')

AST 為

{
  "type": "Program",
  "start": 0, // 起始位置
  "end": 16, // 結(jié)束位置,字符長(zhǎng)度
  "body": [
    {
      "type": "ExpressionStatement", // 表達(dá)式語句
      "start": 0,
      "end": 16,
      "expression": {
        "type": "CallExpression", // 函數(shù)方法調(diào)用式
        "start": 0,
        "end": 16,
        "callee": {
          "type": "MemberExpression", // 成員表達(dá)式 console.log
          "start": 0,
          "end": 11,
          "object": {
            "type": "Identifier", // 標(biāo)識(shí)符,可以是表達(dá)式或者結(jié)構(gòu)模式
            "start": 0,
            "end": 7,
            "name": "console"
          },
          "property": {
            "type": "Identifier", 
            "start": 8,
            "end": 11,
            "name": "log"
          },
          "computed": false, // 成員表達(dá)式的計(jì)算結(jié)果,如果為 true 則是 console[log], false 則為 console.log
          "optional": false
        },
        "arguments": [ // 參數(shù)
          {
            "type": "Literal", // 文字標(biāo)記,可以是表達(dá)式
            "start": 12,
            "end": 15,
            "value": "1",
            "raw": "'1'"
          }
        ],
        "optional": false
      }
    }
  ],
  "sourceType": "module"
}

看兩個(gè)稍微復(fù)雜的代碼

const b = { a: 1 };
const { a } = b;
function add(a, b) {
    return a + b;
}

這里建議讀者自己將上述代碼復(fù)制進(jìn)上面提到的網(wǎng)站中,自行理解 estree 的各種節(jié)點(diǎn)類型。當(dāng)然了,我們也不可能看一篇文章就記住那么多類型,只要心里有個(gè)大致的概念即可。

認(rèn)識(shí) acorn[2]

由 JavaScript 編寫的 JavaScript 解析器,類似的解析器還有很多,比如 Esprima[3] 和 Shift[4] ,關(guān)于他們的性能,Esprima 的官網(wǎng)給了個(gè)測(cè)試地址[5],但是由于 acron 代碼比較精簡(jiǎn),且 webpack 和 eslint 都依賴 acorn,因此我們這次從 acorn 下手,了解如何使用 AST。

基本操作

acorn 的操作很簡(jiǎn)單

import * as acorn from 'acorn';
const code = 'xxx';
const ast = acorn.parse(code, options)

這樣我們就能拿到代碼的 ast 了,options 的定義如下

  interface Options {
    ecmaVersion: 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 'latest'
    sourceType?: 'script' | 'module'
    onInsertedSemicolon?: (lastTokEnd: number, lastTokEndLoc?: Position) => void
    onTrailingComma?: (lastTokEnd: number, lastTokEndLoc?: Position) => void
    allowReserved?: boolean | 'never'
    allowReturnOutsideFunction?: boolean
    allowImportExportEverywhere?: boolean
    allowAwaitOutsideFunction?: boolean
    allowSuperOutsideMethod?: boolean
    allowHashBang?: boolean
    locations?: boolean
    onToken?: ((token: Token) => any) | Token[]
    onComment?: ((
      isBlock: boolean, text: string, start: number, end: number, startLoc?: Position,
      endLoc?: Position
    ) => void) | Comment[]
    ranges?: boolean
    program?: Node
    sourceFile?: string
    directSourceFile?: string
    preserveParens?: boolean
  }
  • ecmaVersion ECMA 版本,默認(rèn)時(shí) es7
  • locations 默認(rèn)為 false,設(shè)置為 true 時(shí)節(jié)點(diǎn)會(huì)攜帶一個(gè) loc 對(duì)象來表示當(dāng)前開始與結(jié)束的行數(shù)。
  • onComment 回調(diào)函數(shù),每當(dāng)代碼執(zhí)行到注釋的時(shí)候都會(huì)觸發(fā),可以獲取當(dāng)前的注釋內(nèi)容

獲得 ast 之后我們想還原之前的函數(shù)怎么辦,這里可以使用 astring[6]

import * as astring from 'astring';
 
const code = astring.generate(ast);

實(shí)現(xiàn)普通函數(shù)轉(zhuǎn)換為箭頭函數(shù)

接下來我們就可以利用 AST 來實(shí)現(xiàn)一些字符串匹配不太容易實(shí)現(xiàn)的操作,比如將普通函數(shù)轉(zhuǎn)化為箭頭函數(shù)。

我們先來看兩個(gè)函數(shù)的AST有什么區(qū)別

function add(a, b) {
    return a + b;
}
const add = (a, b) => {
    return a + b;
}
{
  "type": "Program",
  "start": 0,
  "end": 41,
  "body": [
    {
      "type": "FunctionDeclaration",
      "start": 0,
      "end": 40,
      "id": {
        "type": "Identifier",
        "start": 9,
        "end": 12,
        "name": "add"
      },
      "expression": false,
      "generator": false,
      "async": false,
      "params": [
        {
          "type": "Identifier",
          "start": 13,
          "end": 14,
          "name": "a"
        },
        {
          "type": "Identifier",
          "start": 16,
          "end": 17,
          "name": "b"
        }
      ],
      "body": {
        "type": "BlockStatement",
        "start": 19,
        "end": 40,
        "body": [
          {
            "type": "ReturnStatement",
            "start": 25,
            "end": 38,
            "argument": {
              "type": "BinaryExpression",
              "start": 32,
              "end": 37,
              "left": {
                "type": "Identifier",
                "start": 32,
                "end": 33,
                "name": "a"
              },
              "operator": "+",
              "right": {
                "type": "Identifier",
                "start": 36,
                "end": 37,
                "name": "b"
              }
            }
          }
        ]
      }
    }
  ],
  "sourceType": "module"
}
{
  "type": "Program",
  "start": 0,
  "end": 43,
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 0,
      "end": 43,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 6,
          "end": 43,
          "id": {
            "type": "Identifier",
            "start": 6,
            "end": 9,
            "name": "add"
          },
          "init": {
            "type": "ArrowFunctionExpression",
            "start": 12,
            "end": 43,
            "id": null,
            "expression": false,
            "generator": false,
            "async": false,
            "params": [
              {
                "type": "Identifier",
                "start": 13,
                "end": 14,
                "name": "a"
              },
              {
                "type": "Identifier",
                "start": 16,
                "end": 17,
                "name": "b"
              }
            ],
            "body": {
              "type": "BlockStatement",
              "start": 22,
              "end": 43,
              "body": [
                {
                  "type": "ReturnStatement",
                  "start": 28,
                  "end": 41,
                  "argument": {
                    "type": "BinaryExpression",
                    "start": 35,
                    "end": 40,
                    "left": {
                      "type": "Identifier",
                      "start": 35,
                      "end": 36,
                      "name": "a"
                    },
                    "operator": "+",
                    "right": {
                      "type": "Identifier",
                      "start": 39,
                      "end": 40,
                      "name": "b"
                    }
                  }
                }
              ]
            }
          }
        }
      ],
      "kind": "const"
    }
  ],
  "sourceType": "module"
}

找到區(qū)別之后我們就可以有大致的思路

  1. 找到 FunctionDeclaration
  2. 將其替換為VariableDeclaration VariableDeclarator 節(jié)點(diǎn)
  3. 在 VariableDeclarator 節(jié)點(diǎn)的 init 屬性下新建 ArrowFunctionExpression 節(jié)點(diǎn)
  4. 并將 FunctionDeclaration 節(jié)點(diǎn)的相關(guān)屬性替換到 ArrowFunctionExpression 上即可

但是由于 acorn 處理的 ast 只是單純的對(duì)象,并不具備類似 dom 節(jié)點(diǎn)之類的對(duì)節(jié)點(diǎn)的操作能力,如果需要操作節(jié)點(diǎn),需要寫很多工具函數(shù), 所以我這里就簡(jiǎn)單寫一下。

import * as acorn from "acorn";
import * as astring from 'astring';
import { createNode, walkNode } from "./utils.js";
 
const code = 'function add(a, b) { return a+b; } function dd(a) { return a + 1 }';
console.log('in:', code);
const ast = acorn.parse(code);
 
walkNode(ast, (node) => {
    if(node.type === 'FunctionDeclaration') {
        node.type = 'VariableDeclaration';
        const variableDeclaratorNode = createNode('VariableDeclarator');
        variableDeclaratorNode.id = node.id;
        delete node.id;
        const arrowFunctionExpressionNode = createNode('ArrowFunctionExpression');
        arrowFunctionExpressionNode.params = node.params;
        delete node.params;
        arrowFunctionExpressionNode.body = node.body;
        delete node.body;
        variableDeclaratorNode.init = arrowFunctionExpressionNode;
        node.declarations = [variableDeclaratorNode];
        node.kind = 'const';
    }
})
 
console.log('out:', astring.generate(ast))

結(jié)果如下

如果想要代碼更加健壯,可以使用 recast[7],提供了對(duì) ast 的各種操作

// 用螺絲刀解析機(jī)器
const ast = recast.parse(code);
 
// ast可以處理很巨大的代碼文件
// 但我們現(xiàn)在只需要代碼塊的第一個(gè)body,即add函數(shù)
const add  = ast.program.body[0]
 
console.log(add)
 
// 引入變量聲明,變量符號(hào),函數(shù)聲明三種“模具”
const {variableDeclaration, variableDeclarator, functionExpression} = recast.types.builders
 
// 將準(zhǔn)備好的組件置入模具,并組裝回原來的ast對(duì)象。
ast.program.body[0] = variableDeclaration("const", [
  variableDeclarator(add.id, functionExpression(
    null, // Anonymize the function expression.
    add.params,
    add.body
  ))
]);
 
//將AST對(duì)象重新轉(zhuǎn)回可以閱讀的代碼
const output = recast.print(ast).code;
 
console.log(output)

這里只是示例代碼,展示 recast 的一些操作,最好的情況還是能遍歷節(jié)點(diǎn)自動(dòng)替換。

這樣我們就完成了將普通函數(shù)轉(zhuǎn)換成箭頭函數(shù)的操作,但 ast 的作用不止于此,作為一個(gè)前端在工作中可能涉及 ast 的地方,就是自定義 eslint 、 stylelint 等插件。

到此這篇關(guān)于關(guān)于前端要知道的 AST知識(shí)的文章就介紹到這了,更多相關(guān)前端AST知識(shí)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論