日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

【js】JavaScript parser实现浅析

發(fā)布時(shí)間:2023/12/15 综合教程 30 生活家
生活随笔 收集整理的這篇文章主要介紹了 【js】JavaScript parser实现浅析 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

  最近筆者的團(tuán)隊(duì)遷移了webpack2,在遷移過程中,筆者發(fā)現(xiàn)webpack2中有相當(dāng)多的兼容代碼,雖然外界有很多聲音一直在質(zhì)疑作者為什么要破壞性更新,其實(shí)大家也都知道webpack1那種過于“靈活”的配置方式是有待商榷的,所以作者才會(huì)在webpack2上進(jìn)行了很多規(guī)范,但是,筆者卻隱隱的覺得,等到webpack3的時(shí)候,估計(jì)會(huì)有更多的破壞性更新,不然也不會(huì)有這個(gè)webpack2了。于是心中有關(guān)webpack的話題便也擱置了,且等它更穩(wěn)定一些,再談不遲,今天先來講講在劇烈的版本變化中,不變的部分。

  大家都知道,webpack是做模塊綁定用的,那么就不得不牽涉到語法解析的內(nèi)容,而且其極高的擴(kuò)展性,也往往需要依賴于語法解析,而在webpack內(nèi)部使用acorn做語法解析,類似的還有babel使用的babylon,今天就帶來兩者的簡(jiǎn)要分析。

  官方給兩者的定義都叫做JavaScript parser,內(nèi)部也一致的使用了AST(Abstract syntax tree,即抽象語法樹)的概念。如果對(duì)這個(gè)概念不明白的同學(xué)可以參考WIKIAST的解釋

  因?yàn)閎abylon引用了flow,eslint等一些checker,所以整個(gè)項(xiàng)目結(jié)構(gòu)相當(dāng)?shù)囊?guī)范,筆者僅已7.0.0為例:

  文件夾目錄如下:

index.js //程序入口,會(huì)調(diào)用parser進(jìn)行初始化
types.js //定義了基本類型和接口
options.js //定義獲取配置的方法以及配置的缺省值
parser //所有的parser都在此
  index.js //parser入口類,繼承自 StatementParser 即 ./statement.js
  statement.js //聲明StatementParser 繼承自 ExpressionParser 即 ./expression.js
  expression.js //聲明ExpressionParser 繼承自 LValParser 即 ./lval.js
  lval.js //聲明 LValParser 繼承自 NodeUtils 即 ./node.js
  node.js //聲明 NodeUtils 繼承自 UtilParser 即 ./util.js, 同時(shí)還實(shí)現(xiàn)了上一級(jí)目錄中types.js 的nodebase接口為Node類
  util.js //聲明 UtilParser 繼承自 Tokenizer 即 ../tokenizer/index.js
  location.js //聲明 LocationParser 主要用于拋出異常 繼承自 CommentsParser 即./comments.js
  comments.js //聲明 CommentsParser 繼承自 BaseParser 即./base.js
  base.js //所有parser的基類
plugins
tokenizer
  index.js //定義了 Token類 繼承自上級(jí)目錄parser的LocationParser 即 ../parser/location.js
util

  大概流程是這樣的:

1、首先調(diào)用index.js的parse;
2、實(shí)例化一個(gè)parser對(duì)象,調(diào)用parser對(duì)象的parse方法,開始轉(zhuǎn)換;
3、初始化node開始構(gòu)造ast;
  1) node.js 初始化node
  2) tokenizer.js 初始化token
  3) statement.js 調(diào)用 parseBlockBody,開始解析。這個(gè)階段會(huì)構(gòu)造File根節(jié)點(diǎn)和program節(jié)點(diǎn),并在parse完成之后閉合
  4) 執(zhí)行parseStatement, 將已經(jīng)合法的節(jié)點(diǎn)插入到body中。這個(gè)階段會(huì)產(chǎn)生各種*Statement type的節(jié)點(diǎn)
  5)分解statement, parseExpression。這個(gè)階段除了產(chǎn)生各種expression的節(jié)點(diǎn)以外,還將將產(chǎn)生type為Identifier的節(jié)點(diǎn)
  6) 將上步驟中生成的原子表達(dá)式,調(diào)用toAssignable ,將其參數(shù)歸類
4、迭代過程完成后,封閉節(jié)點(diǎn),完成body閉合

  不過在筆者看來,babylon的parser實(shí)現(xiàn)似乎并不能稱得上是一個(gè)很好的實(shí)現(xiàn),而實(shí)現(xiàn)中往往還會(huì)使用的forwarddeclaration(類似虛函數(shù)的概念),如下圖

  

  一個(gè)“+”在方法前面的感覺就像是要以前的IIFE一樣。。

  有點(diǎn)扯遠(yuǎn)了,總的來說依然是傳統(tǒng)語法分析的幾個(gè)步驟,不過筆者在讀源碼的時(shí)候一直覺得蠻奇怪的為何他們內(nèi)部要使用繼承來實(shí)現(xiàn)parser,parser的場(chǎng)景更像是mixin或者高階函數(shù)的場(chǎng)景,不過后者在具體處理中確實(shí)沒有繼承那樣清晰的結(jié)構(gòu)。

  說了這么多,babylon最后會(huì)生成什么呢?以es2016的冪運(yùn)算“3 ** 2”為例:

{
  "type": "File",
  "start": 0,
  "end": 7,
  "loc": {
    "start": {
      "line": 1,
      "column": 0
    },
    "end": {
      "line": 1,
      "column": 7
    }
  },
  "program": {
    "type": "Program",
    "start": 0,
    "end": 7,
    "loc": {
      "start": {
        "line": 1,
        "column": 0
      },
      "end": {
        "line": 1,
        "column": 7
      }
    },
    "sourceType": "script",
    "body": [
      {
        "type": "ExpressionStatement",
        "start": 0,
        "end": 7,
        "loc": {
          "start": {
            "line": 1,
            "column": 0
          },
          "end": {
            "line": 1,
            "column": 7
          }
        },
        "expression": {
          "type": "BinaryExpression",
          "start": 0,
          "end": 6,
          "loc": {
            "start": {
              "line": 1,
              "column": 0
            },
            "end": {
              "line": 1,
              "column": 6
            }
          },
          "left": {
            "type": "NumericLiteral",
            "start": 0,
            "end": 1,
            "loc": {
              "start": {
                "line": 1,
                "column": 0
              },
              "end": {
                "line": 1,
                "column": 1
              }
            },
            "extra": {
              "rawValue": 3,
              "raw": "3"
            },
            "value": 3
          },
          "operator": "**",
          "right": {
            "type": "NumericLiteral",
            "start": 5,
            "end": 6,
            "loc": {
              "start": {
                "line": 1,
                "column": 5
              },
              "end": {
                "line": 1,
                "column": 6
              }
            },
            "extra": {
              "rawValue": 2,
              "raw": "2"
            },
            "value": 2
          }
        }
      }
    ],
    "directives": []
  }
}

完整的列表看著未免有些可怕,筆者將有關(guān)location信息的去除之后,構(gòu)造了以下這個(gè)對(duì)象:

{
    "type": "File",
    "program": {
        "type": "Program",
        "sourceType": "script",
        "body": [{
            "type": "ExpressionStatement",
            "expression": {
                "type": "BinaryExpression",
                "left": {
                    "type": "NumericLiteral",
                    "value": 3
                },
                "operator": "**",
                "right": {
                    "type": "NumericLiteral",
                    "value": 2
                }
            }
        }]
    }
}

  可以看出,這個(gè)類AST的的對(duì)象是內(nèi)部,大部分內(nèi)容是其實(shí)是有關(guān)位置的信息,因?yàn)楹艽蟪潭壬希枰赃@些信息去描述這個(gè)node的具體作用。

  然后讓我們?cè)賮砜纯磜ebpack使用的acorn:

  也許是acorn的作者和筆者有類似閱讀babylon的經(jīng)歷,覺得這種實(shí)現(xiàn)不太友好。。于是,acorn的作者用了更為簡(jiǎn)單直接的實(shí)現(xiàn):

index.js //程序入口 引用了 ./state.js 的Parser類
state.js //構(gòu)造Parser類
parseutil.js //向Parser類 添加有關(guān) UtilParser 的方法
statement.js //向Parser類 添加有關(guān) StatementParser 的方法
lval.js //向Parser類 添加有關(guān) LValParser 的方法
expression.js //向Parser類 添加有關(guān) ExpressionParser 的方法
location.js //向Parser類 添加有關(guān) LocationParser 的方法
scope.js //向Parser類 添加處理scope的方法

identifier.js 
locutil.js
node.js
options.js
tokencontext.js
tokenize.js
tokentype.js
util.js
whitespace.js

  雖然內(nèi)部實(shí)現(xiàn)基本是類似的,有很多連方法名都是一致的(注釋中使用的類名在acorn中并沒有實(shí)現(xiàn),只是表示具有某種功能的方法的集合),但是在具體實(shí)現(xiàn)上,acorn不可謂不暴力,連多余的目錄都沒有,所有文件全在src目錄下,其中值得一提的是它并沒有使用繼承的方式,而是使用了對(duì)象擴(kuò)展的方式來實(shí)現(xiàn)的Parser類,如下圖:

在具體的文件中,直接擴(kuò)展Paser的prototype

  

  沒想到筆者之前戲談的mixin的方式真的就這樣被使用了,然而mixin的可讀性一定程度上還要差,經(jīng)歷過類似ReactComponentWithPureRenderMixin的同學(xué)想必印象尤深。

  不過話說回來,acorn內(nèi)部實(shí)現(xiàn)與babylon并無二致,連調(diào)用的方法名都是類似的,不過acorn多實(shí)現(xiàn)了一個(gè)scope的概念,用于限制作用域。

  緊接著我們來看一下acorn生成的結(jié)果,以“x ** y”為例:

  

{
    type: "Program",
    body: [{
        type: "ExpressionStatement",
        expression: {
            type: "BinaryExpression",
            left: {
                type: "Identifier",
                name: "x",
                loc: {
                    start: {
                        line: 1,
                        column: 0
                    },
                    end: {
                        line: 1,
                        column: 1
                    }
                }
            },
            operator: "**",
            right: {
                type: "Identifier",
                name: "y",
                loc: {
                    start: {
                        line: 1,
                        column: 5
                    },
                    end: {
                        line: 1,
                        column: 6
                    }
                }
            },
            loc: {
                start: {
                    line: 1,
                    column: 0
                },
                end: {
                    line: 1,
                    column: 6
                }
            }
        },
        loc: {
            start: {
                line: 1,
                column: 0
            },
            end: {
                line: 1,
                column: 6
            }
        }
    }],
    loc: {
        start: {
            line: 1,
            column: 0
        },
        end: {
            line: 1,
            column: 6
        }
    }
}, {
    ecmaVersion: 7,
    locations: true
}

可以看出,大部分內(nèi)容依然是位置信息,我們照例去掉它們:

{
    type: "Program",
    body: [{
        type: "ExpressionStatement",
        expression: {
            type: "BinaryExpression",
            left: {
                type: "Identifier",
                name: "x",
            },
            operator: "**",
            right: {
                type: "Identifier",
                name: "y",

            }
        }
    }]
}

  除去一些參數(shù)上的不同,最大的區(qū)別可能就是最外層babylon還有一個(gè)File節(jié)點(diǎn),而acorn的根節(jié)點(diǎn)就是program了,畢竟babel和webpack的工作場(chǎng)景還是略有區(qū)別的。

  也許,僅聽筆者講述一切都那么簡(jiǎn)單,然而這只是理想情況,現(xiàn)實(shí)的復(fù)雜遠(yuǎn)超我們的想象,簡(jiǎn)單的舉個(gè)印象比較深的例子,在兩個(gè)parser都有有關(guān)whitespace的抽象,主要是用于提供一些匹配換行符的正則,通常都想到的是:

/
?|
/

但實(shí)際上完整的卻是

/
?|
|u2028|u2029/

而且考慮到ASCII碼的情況,還需要很糾結(jié)的枚舉出非空格的情況

/[u1680u180eu2000-u200au202fu205fu3000ufeff]/

  因?yàn)閜arse處理的是我們實(shí)際開發(fā)中自己coding的代碼,不同的人不同的風(fēng)格,會(huì)有怎么樣的奇怪的方式其實(shí)是非常考驗(yàn)完備性思維的一項(xiàng)工作,而且這往往比我們?nèi)粘5臉I(yè)務(wù)工作的場(chǎng)景更為復(fù)雜,它很多時(shí)候甚至是接近一個(gè)可能性的全集,而并非“大概率可能”的一個(gè)集合。雖然我們?nèi)粘9ぷ鬟@種parser幾乎是透明的,我們?cè)趇nit的前端項(xiàng)目時(shí)基本已經(jīng)部署好了開發(fā)環(huán)境,但是對(duì)于某些情況下的實(shí)際問題定位,卻又有非凡的意義,而且,這還在一定時(shí)間內(nèi)是一個(gè)常態(tài),雖然可能在不久的未來,就會(huì)有更加智能更加強(qiáng)大的前端IDE。

  有關(guān)ast的實(shí)驗(yàn),可以試一下這個(gè)網(wǎng)站:https://astexplorer.net/

總結(jié)

以上是生活随笔為你收集整理的【js】JavaScript parser实现浅析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。