国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

使用 Acorn 來解析 JavaScript

U2FsdGVkX1x / 2549人閱讀

摘要:下面進(jìn)入正題,如何使用來解析。解析器的都是很簡單的的配置項(xiàng)蠻多的,里邊還包括了一些事件可以設(shè)置回調(diào)函數(shù)。傳入一個(gè)回調(diào)函數(shù),每當(dāng)解析到代碼中的注釋時(shí)會(huì)觸發(fā),可以獲取當(dāng)年注釋內(nèi)容,參數(shù)列表是。當(dāng)屬性是時(shí),則是表示這個(gè)節(jié)點(diǎn)。

Talk

因?yàn)樽罱ぷ魃嫌行枰褂媒馕?JavaScript 的代碼,大部分情況使用正則表達(dá)式匹配就可以處理,但是一旦依賴于代碼上下文的內(nèi)容時(shí),正則或者簡單的字符解析就很力不從心了,這個(gè)時(shí)候需要一個(gè)語言解析器來獲取整一個(gè) AST(abstract syntax tree)。

然后我找到了多個(gè)使用 JavaScript 編寫的 JavaScript 解析器:

Esprima

Acorn

UglifyJS 2

Shift

從提交記錄來看,維護(hù)情況都蠻好的,ES 各種發(fā)展的特性都跟得上,我分別都簡單了解了一下,聊聊他們的一些情況。

Esprima 是很經(jīng)典的一個(gè)解析器,Acorn 在它之后誕生,都是幾年前的事情了。按照 Acorn 作者的說法,當(dāng)時(shí)造這個(gè)輪子更多只是好玩,速度可以和 Esprima 媲美,但是實(shí)現(xiàn)代碼更少。其中比較關(guān)鍵的點(diǎn)是這兩個(gè)解析器出來的 AST 結(jié)果(對(duì),只是 AST,tokens 不一樣)都是符合 The Estree Spec 規(guī)范(這是 Mozilla 的工程師給出的 SpiderMonkey 引擎輸出的 JavaScript AST 的規(guī)范文檔,也可以參考:SpiderMonkey in MDN)的,也就是得到的結(jié)果在很大部分上是兼容的。

現(xiàn)在很出名的 Webpack 解析代碼時(shí)用的也是 Acorn。

至于 Uglify,很出名的一個(gè) JavaScript 代碼壓縮器,其實(shí)它自帶了一個(gè)代碼解析器,也可以輸出 AST,但是它的功能更多還是用于壓縮代碼,如果拿來解析代碼感覺不夠純粹。

Shift 這個(gè)沒做多少了解,只知道他定義了自己的一套 AST 規(guī)范。

Esprima 官網(wǎng)上有一個(gè)性能測(cè)試,我在 chrome 上跑的結(jié)果如下:

可見,Acorn 的性能很不錯(cuò),而且還有一個(gè) Estree 的規(guī)范呢(規(guī)范很重要,我個(gè)人覺得遵循通用的規(guī)范是代碼復(fù)用的重要基礎(chǔ)),所以我就直接選用 Acorn 來做代碼解析了。

圖中做性能對(duì)比的還有 Google 的 Traceur,它更多是一個(gè) ES6 to ES5 的 compiler,于我們想要找的解析器定位不符。

下面進(jìn)入正題,如何使用 Acorn 來解析 JavaScript。

API

解析器的 API 都是很簡單的:

const ast = acorn.parse(code, options)

Acorn 的配置項(xiàng)蠻多的,里邊還包括了一些事件可以設(shè)置回調(diào)函數(shù)。我們挑幾個(gè)比較重要的講下:

ecmaVersion
字面意義,很好理解,就是設(shè)置你要解析的 JavaScript 的 ECMA 版本。默認(rèn)是 ES7。

sourceType
這個(gè)配置項(xiàng)有兩個(gè)值:modulescript,默認(rèn)是 script

主要是嚴(yán)格模式和 import/export 的區(qū)別。ES6 中的模塊是嚴(yán)格模式,也就是你無須添加 use strict。我們通常瀏覽器中使用的 script 是沒有 import/export 語法的。
所以,選擇了 script 則出現(xiàn) import/export 會(huì)報(bào)錯(cuò),可以使用嚴(yán)格模式聲明,選擇了 module,則不用嚴(yán)格模式聲明,可以使用 import/export 語法。

locations
默認(rèn)值是 false,設(shè)置為 true 之后會(huì)在 AST 的節(jié)點(diǎn)中攜帶多一個(gè) loc 對(duì)象來表示當(dāng)前的開始和結(jié)束的行數(shù)和列數(shù)。

onComment
傳入一個(gè)回調(diào)函數(shù),每當(dāng)解析到代碼中的注釋時(shí)會(huì)觸發(fā),可以獲取當(dāng)年注釋內(nèi)容,參數(shù)列表是:[block, text, start, end]

block 表示是否是塊注釋,text 是注釋內(nèi)容,startend 是注釋開始和結(jié)束的位置。

上邊提及的 Espree 需要 Esprima 的 attachComment 的配置項(xiàng),設(shè)置為 true 后,Esprima 會(huì)在代碼解析結(jié)果的節(jié)點(diǎn)中攜帶注釋相關(guān)信息(trailingCommentsleadingComments)。Espree 則是利用 Acorn 的 onComment 配置來實(shí)現(xiàn)這個(gè) Esprima 特性的兼容。

解析器通常還會(huì)有一個(gè)獲取詞法分析結(jié)果的接口:

const tokens = [...acorn.tokenizer(code, options)]

tokenizer 方法的第二個(gè)參數(shù)也能夠配置 locations

詞法結(jié)果 token 和 Esprima 的結(jié)果數(shù)據(jù)結(jié)構(gòu)上有一定的區(qū)別(Espree 又是做了這一層的兼容),有興趣了解的可以看下 Esprima 的解析結(jié)果:http://esprima.org/demo/parse... 。

至于 Acorn 解析的 AST 和 token 的內(nèi)容我們接下來詳述。

Token

我找了半天,沒找到關(guān)于 token 數(shù)據(jù)結(jié)構(gòu)的詳細(xì)介紹,只能自己動(dòng)手來看一下了。

我用來測(cè)試解析的代碼是:

import "hello.js"

var a = 2;

// test
function name() { console.log(arguments); }

解析出來的 token 數(shù)組是一個(gè)個(gè)類似這樣的對(duì)象:

Token {
    type:
     TokenType {
       label: "import",
       keyword: "import",
       beforeExpr: false,
       startsExpr: false,
       isLoop: false,
       isAssign: false,
       prefix: false,
       postfix: false,
       binop: null,
       updateContext: null },
    value: "import",
    start: 5,
    end: 11 },

看上去其實(shí)很好理解對(duì)不對(duì),在 type 對(duì)應(yīng)的對(duì)象中,label 表示當(dāng)前標(biāo)識(shí)的一個(gè)類型,keyword 就是關(guān)鍵詞,像例子中的 import,或者 function 之類的。

value 則是當(dāng)前標(biāo)識(shí)的值,start/end 分別是開始和結(jié)束的位置。

通常我們需要關(guān)注的就是 label/keyword/value 這些了。其他的詳細(xì)可以參考源碼:tokentype.js。

The Estree Spec

這一部分是重頭戲,因?yàn)閷?shí)際上我需要的還是解析出來的 AST。最原滋原味的內(nèi)容來自于:The Estree Spec,我只是閱讀了之后的搬運(yùn)工。

提供了標(biāo)準(zhǔn)文檔的好處是,很多東西有跡可循,這里還有一個(gè)工具,用于把滿足 Estree 標(biāo)準(zhǔn)的 AST 轉(zhuǎn)換為 ESMAScript 代碼:escodegen。

好吧,回到正題,我們先來看一下 ES5 的部分,可以在 Esprima: Parser 這個(gè)頁面測(cè)試各種代碼的解析結(jié)果。

符合這個(gè)規(guī)范的解析出來的 AST 節(jié)點(diǎn)用 Node 對(duì)象來標(biāo)識(shí),Node 對(duì)象應(yīng)該符合這樣的接口:

interface Node {
    type: string;
    loc: SourceLocation | null;
}

type 字段表示不同的節(jié)點(diǎn)類型,下邊會(huì)再講一下各個(gè)類型的情況,分別對(duì)應(yīng)了 JavaScript 中的什么語法。
loc 字段表示源碼的位置信息,如果沒有相關(guān)信息的話為 null,否則是一個(gè)對(duì)象,包含了開始和結(jié)束的位置。接口如下:

interface SourceLocation {
    source: string | null;
    start: Position;
    end: Position;
}

這里的 Position 對(duì)象包含了行和列的信息,行從 1 開始,列從 0 開始:

interface Position {
    line: number; // >= 1
    column: number; // >= 0
}

好了,基礎(chǔ)部分就是這樣,接下來看各種類型的節(jié)點(diǎn),順帶溫習(xí)一下 JavaScript 語法的一些東西吧。對(duì)于這里每一部分的內(nèi)容,會(huì)簡單談一下,但不會(huì)展開(內(nèi)容不少),對(duì) JavaScript 了解的人很容易就明白的。

我覺得看完就像把 JavaScript 的基礎(chǔ)語法整理了一遍。

Identifier

標(biāo)識(shí)符,我覺得應(yīng)該是這么叫的,就是我們寫 JS 時(shí)自定義的名稱,如變量名,函數(shù)名,屬性名,都?xì)w為標(biāo)識(shí)符。相應(yīng)的接口是這樣的:

interface Identifier <: Expression, Pattern {
    type: "Identifier";
    name: string;
}

一個(gè)標(biāo)識(shí)符可能是一個(gè)表達(dá)式,或者是解構(gòu)的模式(ES6 中的解構(gòu)語法)。我們等會(huì)會(huì)看到 ExpressionPattern 相關(guān)的內(nèi)容的。

Literal

字面量,這里不是指 [] 或者 {} 這些,而是本身語義就代表了一個(gè)值的字面量,如 1“hello”, true 這些,還有正則表達(dá)式(有一個(gè)擴(kuò)展的 Node 來表示正則表達(dá)式),如 /d?/。我們看一下文檔的定義:

interface Literal <: Expression {
    type: "Literal";
    value: string | boolean | null | number | RegExp;
}

value 這里即對(duì)應(yīng)了字面量的值,我們可以看出字面量值的類型,字符串,布爾,數(shù)值,null 和正則。

RegExpLiteral

這個(gè)針對(duì)正則字面量的,為了更好地來解析正則表達(dá)式的內(nèi)容,添加多一個(gè) regex 字段,里邊會(huì)包括正則本身,以及正則的 flags

interface RegExpLiteral <: Literal {
  regex: {
    pattern: string;
    flags: string;
  };
}
Programs

一般這個(gè)是作為跟節(jié)點(diǎn)的,即代表了一棵完整的程序代碼樹。

interface Program <: Node {
    type: "Program";
    body: [ Statement ];
}

body 屬性是一個(gè)數(shù)組,包含了多個(gè) Statement(即語句)節(jié)點(diǎn)。

Functions

函數(shù)聲明或者函數(shù)表達(dá)式節(jié)點(diǎn)。

interface Function <: Node {
    id: Identifier | null;
    params: [ Pattern ];
    body: BlockStatement;
}

id 是函數(shù)名,params 屬性是一個(gè)數(shù)組,表示函數(shù)的參數(shù)。body 是一個(gè)塊語句。

有一個(gè)值得留意的點(diǎn)是,你在測(cè)試過程中,是不會(huì)找到 type: "Function" 的節(jié)點(diǎn)的,但是你可以找到 type: "FunctionDeclaration"type: "FunctionExpression",因?yàn)楹瘮?shù)要么以聲明語句出現(xiàn),要么以函數(shù)表達(dá)式出現(xiàn),都是節(jié)點(diǎn)類型的組合類型,后邊會(huì)再提及 FunctionDeclarationFunctionExpression 的相關(guān)內(nèi)容。

這讓人感覺這個(gè)文檔規(guī)劃得蠻細(xì)致的,函數(shù)名,參數(shù)和函數(shù)塊是屬于函數(shù)部分的內(nèi)容,而聲明或者表達(dá)式則有它自己需要的東西。

Statement

語句節(jié)點(diǎn)沒什么特別的,它只是一個(gè)節(jié)點(diǎn),一種區(qū)分,但是語句有很多種,下邊會(huì)詳述。

interface Statement <: Node { }
ExpressionStatement

表達(dá)式語句節(jié)點(diǎn),a = a + 1 或者 a++ 里邊會(huì)有一個(gè) expression 屬性指向一個(gè)表達(dá)式節(jié)點(diǎn)對(duì)象(后邊會(huì)提及表達(dá)式)。

interface ExpressionStatement <: Statement {
    type: "ExpressionStatement";
    expression: Expression;
}
BlockStatement

塊語句節(jié)點(diǎn),舉個(gè)例子:if (...) { // 這里是塊語句的內(nèi)容 },塊里邊可以包含多個(gè)其他的語句,所以有一個(gè) body 屬性,是一個(gè)數(shù)組,表示了塊里邊的多個(gè)語句。

interface BlockStatement <: Statement {
    type: "BlockStatement";
    body: [ Statement ];
}
EmptyStatement

一個(gè)空的語句節(jié)點(diǎn),沒有執(zhí)行任何有用的代碼,例如一個(gè)多帶帶的分號(hào) ;

interface EmptyStatement <: Statement {
    type: "EmptyStatement";
}
DebuggerStatement

debugger,就是表示這個(gè),沒有其他了。

interface DebuggerStatement <: Statement {
    type: "DebuggerStatement";
}
WithStatement

with 語句節(jié)點(diǎn),里邊有兩個(gè)特別的屬性,object 表示 with 要使用的那個(gè)對(duì)象(可以是一個(gè)表達(dá)式),body 則是對(duì)應(yīng) with 后邊要執(zhí)行的語句,一般會(huì)是一個(gè)塊語句。

interface WithStatement <: Statement {
    type: "WithStatement";
    object: Expression;
    body: Statement;
}

下邊是控制流的語句:

ReturnStatement

返回語句節(jié)點(diǎn),argument 屬性是一個(gè)表達(dá)式,代表返回的內(nèi)容。

interface ReturnStatement <: Statement {
    type: "ReturnStatement";
    argument: Expression | null;
}
LabeledStatement

label 語句,平時(shí)可能會(huì)比較少接觸到,舉個(gè)例子:

loop: for(let i = 0; i < len; i++) {
    // ...
    for (let j = 0; j < min; j++) {
        // ...
        break loop;
    }
}

這里的 loop 就是一個(gè) label 了,我們可以在循環(huán)嵌套中使用 break loop 來指定跳出哪個(gè)循環(huán)。所以這里的 label 語句指的就是 loop: ... 這個(gè)。

一個(gè) label 語句節(jié)點(diǎn)會(huì)有兩個(gè)屬性,一個(gè) label 屬性表示 label 的名稱,另外一個(gè) body 屬性指向?qū)?yīng)的語句,通常是一個(gè)循環(huán)語句或者 switch 語句。

interface LabeledStatement <: Statement {
    type: "LabeledStatement";
    label: Identifier;
    body: Statement;
}
BreakStatement

break 語句節(jié)點(diǎn),會(huì)有一個(gè) label 屬性表示需要的 label 名稱,當(dāng)不需要 label 的時(shí)候(通常都不需要),便是 null

interface BreakStatement <: Statement {
    type: "BreakStatement";
    label: Identifier | null;
}
ContinueStatement

continue 語句節(jié)點(diǎn),和 break 類似。

interface ContinueStatement <: Statement {
    type: "ContinueStatement";
    label: Identifier | null;
}

下邊是條件語句:

IfStatement

if 語句節(jié)點(diǎn),很常見,會(huì)帶有三個(gè)屬性,test 屬性表示 if (...) 括號(hào)中的表達(dá)式。

consequent 屬性是表示條件為 true 時(shí)的執(zhí)行語句,通常會(huì)是一個(gè)塊語句。

alternate 屬性則是用來表示 else 后跟隨的語句節(jié)點(diǎn),通常也會(huì)是塊語句,但也可以又是一個(gè) if 語句節(jié)點(diǎn),即類似這樣的結(jié)構(gòu):
if (a) { //... } else if (b) { // ... }
alternate 當(dāng)然也可以為 null

interface IfStatement <: Statement {
    type: "IfStatement";
    test: Expression;
    consequent: Statement;
    alternate: Statement | null;
}
SwitchStatement

switch 語句節(jié)點(diǎn),有兩個(gè)屬性,discriminant 屬性表示 switch 語句后緊隨的表達(dá)式,通常會(huì)是一個(gè)變量,cases 屬性是一個(gè) case 節(jié)點(diǎn)的數(shù)組,用來表示各個(gè) case 語句。

interface SwitchStatement <: Statement {
    type: "SwitchStatement";
    discriminant: Expression;
    cases: [ SwitchCase ];
}
SwitchCase

switchcase 節(jié)點(diǎn)。test 屬性代表這個(gè) case 的判斷表達(dá)式,consequent 則是這個(gè) case 的執(zhí)行語句。

當(dāng) test 屬性是 null 時(shí),則是表示 default 這個(gè) case 節(jié)點(diǎn)。

interface SwitchCase <: Node {
    type: "SwitchCase";
    test: Expression | null;
    consequent: [ Statement ];
}

下邊是異常相關(guān)的語句:

ThrowStatement

throw 語句節(jié)點(diǎn),argument 屬性用以表示 throw 后邊緊跟的表達(dá)式。

interface ThrowStatement <: Statement {
    type: "ThrowStatement";
    argument: Expression;
}
TryStatement

try 語句節(jié)點(diǎn),block 屬性表示 try 的執(zhí)行語句,通常是一個(gè)塊語句。

hanlder 屬性是指 catch 節(jié)點(diǎn),finalizer 是指 finally 語句節(jié)點(diǎn),當(dāng) hanldernull 時(shí),finalizer 必須是一個(gè)塊語句節(jié)點(diǎn)。

interface TryStatement <: Statement {
    type: "TryStatement";
    block: BlockStatement;
    handler: CatchClause | null;
    finalizer: BlockStatement | null;
}
CatchClause

catch 節(jié)點(diǎn),param 用以表示 catch 后的參數(shù),body 則表示 catch 后的執(zhí)行語句,通常是一個(gè)塊語句。

interface CatchClause <: Node {
    type: "CatchClause";
    param: Pattern;
    body: BlockStatement;
}

下邊是循環(huán)語句:

WhileStatement

while 語句節(jié)點(diǎn),test 表示括號(hào)中的表達(dá)式,body 是表示要循環(huán)執(zhí)行的語句。

interface WhileStatement <: Statement {
    type: "WhileStatement";
    test: Expression;
    body: Statement;
}
DoWhileStatement

do/while 語句節(jié)點(diǎn),和 while 語句類似。

interface DoWhileStatement <: Statement {
    type: "DoWhileStatement";
    body: Statement;
    test: Expression;
}
ForStatement

for 循環(huán)語句節(jié)點(diǎn),屬性 init/test/update 分別表示了 for 語句括號(hào)中的三個(gè)表達(dá)式,初始化值,循環(huán)判斷條件,每次循環(huán)執(zhí)行的變量更新語句(init 可以是變量聲明或者表達(dá)式)。這三個(gè)屬性都可以為 null,即 for(;;){}
body 屬性用以表示要循環(huán)執(zhí)行的語句。

interface ForStatement <: Statement {
    type: "ForStatement";
    init: VariableDeclaration | Expression | null;
    test: Expression | null;
    update: Expression | null;
    body: Statement;
}
ForInStatement

for/in 語句節(jié)點(diǎn),leftright 屬性分別表示在 in 關(guān)鍵詞左右的語句(左側(cè)可以是一個(gè)變量聲明或者表達(dá)式)。body 依舊是表示要循環(huán)執(zhí)行的語句。

interface ForInStatement <: Statement {
    type: "ForInStatement";
    left: VariableDeclaration |  Pattern;
    right: Expression;
    body: Statement;
}
Declarations

聲明語句節(jié)點(diǎn),同樣也是語句,只是一個(gè)類型的細(xì)化。下邊會(huì)介紹各種聲明語句類型。

interface Declaration <: Statement { }
FunctionDeclaration

函數(shù)聲明,和之前提到的 Function 不同的是,id 不能為 null

interface FunctionDeclaration <: Function, Declaration {
    type: "FunctionDeclaration";
    id: Identifier;
}
VariableDeclaration

變量聲明,kind 屬性表示是什么類型的聲明,因?yàn)?ES6 引入了 const/let
declarations 表示聲明的多個(gè)描述,因?yàn)槲覀兛梢赃@樣:let a = 1, b = 2;

interface VariableDeclaration <: Declaration {
    type: "VariableDeclaration";
    declarations: [ VariableDeclarator ];
    kind: "var";
}
VariableDeclarator

變量聲明的描述,id 表示變量名稱節(jié)點(diǎn),init 表示初始值的表達(dá)式,可以為 null

interface VariableDeclarator <: Node {
    type: "VariableDeclarator";
    id: Pattern;
    init: Expression | null;
}
Expressions

表達(dá)式節(jié)點(diǎn)。

interface Expression <: Node { }
ThisExpression

表示 this

interface ThisExpression <: Expression {
    type: "ThisExpression";
}
ArrayExpression

數(shù)組表達(dá)式節(jié)點(diǎn),elements 屬性是一個(gè)數(shù)組,表示數(shù)組的多個(gè)元素,每一個(gè)元素都是一個(gè)表達(dá)式節(jié)點(diǎn)。

interface ArrayExpression <: Expression {
    type: "ArrayExpression";
    elements: [ Expression | null ];
}
ObjectExpression

對(duì)象表達(dá)式節(jié)點(diǎn),property 屬性是一個(gè)數(shù)組,表示對(duì)象的每一個(gè)鍵值對(duì),每一個(gè)元素都是一個(gè)屬性節(jié)點(diǎn)。

interface ObjectExpression <: Expression {
    type: "ObjectExpression";
    properties: [ Property ];
}
Property

對(duì)象表達(dá)式中的屬性節(jié)點(diǎn)。key 表示鍵,value 表示值,由于 ES5 語法中有 get/set 的存在,所以有一個(gè) kind 屬性,用來表示是普通的初始化,或者是 get/set

interface Property <: Node {
    type: "Property";
    key: Literal | Identifier;
    value: Expression;
    kind: "init" | "get" | "set";
}
FunctionExpression

函數(shù)表達(dá)式節(jié)點(diǎn)。

interface FunctionExpression <: Function, Expression {
    type: "FunctionExpression";
}

下邊是一元運(yùn)算符相關(guān)的表達(dá)式部分:

UnaryExpression

一元運(yùn)算表達(dá)式節(jié)點(diǎn)(++/-- 是 update 運(yùn)算符,不在這個(gè)范疇內(nèi)),operator 表示運(yùn)算符,prefix 表示是否為前綴運(yùn)算符。argument 是要執(zhí)行運(yùn)算的表達(dá)式。

interface UnaryExpression <: Expression {
    type: "UnaryExpression";
    operator: UnaryOperator;
    prefix: boolean;
    argument: Expression;
}
UnaryOperator

一元運(yùn)算符,枚舉類型,所有值如下:

enum UnaryOperator {
    "-" | "+" | "!" | "~" | "typeof" | "void" | "delete"
}
UpdateExpression

update 運(yùn)算表達(dá)式節(jié)點(diǎn),即 ++/--,和一元運(yùn)算符類似,只是 operator 指向的節(jié)點(diǎn)對(duì)象類型不同,這里是 update 運(yùn)算符。

interface UpdateExpression <: Expression {
    type: "UpdateExpression";
    operator: UpdateOperator;
    argument: Expression;
    prefix: boolean;
}
UpdateOperator

update 運(yùn)算符,值為 ++--,配合 update 表達(dá)式節(jié)點(diǎn)的 prefix 屬性來表示前后。

enum UpdateOperator {
    "++" | "--"
}

下邊是二元運(yùn)算符相關(guān)的表達(dá)式部分:

BinaryExpression

二元運(yùn)算表達(dá)式節(jié)點(diǎn),leftright 表示運(yùn)算符左右的兩個(gè)表達(dá)式,operator 表示一個(gè)二元運(yùn)算符。

interface BinaryExpression <: Expression {
    type: "BinaryExpression";
    operator: BinaryOperator;
    left: Expression;
    right: Expression;
}
BinaryOperator

二元運(yùn)算符,所有值如下:

enum BinaryOperator {
    "==" | "!=" | "===" | "!=="
         | "<" | "<=" | ">" | ">="
         | "<<" | ">>" | ">>>"
         | "+" | "-" | "*" | "/" | "%"
         | "|" | "^" | "&" | "in"
         | "instanceof"
}
AssignmentExpression

賦值表達(dá)式節(jié)點(diǎn),operator 屬性表示一個(gè)賦值運(yùn)算符,leftright 是賦值運(yùn)算符左右的表達(dá)式。

interface AssignmentExpression <: Expression {
    type: "AssignmentExpression";
    operator: AssignmentOperator;
    left: Pattern | Expression;
    right: Expression;
}
AssignmentOperator

賦值運(yùn)算符,所有值如下:(常用的并不多)

enum AssignmentOperator {
    "=" | "+=" | "-=" | "*=" | "/=" | "%="
        | "<<=" | ">>=" | ">>>="
        | "|=" | "^=" | "&="
}
LogicalExpression

邏輯運(yùn)算表達(dá)式節(jié)點(diǎn),和賦值或者二元運(yùn)算類型,只不過 operator 是邏輯運(yùn)算符類型。

interface LogicalExpression <: Expression {
    type: "LogicalExpression";
    operator: LogicalOperator;
    left: Expression;
    right: Expression;
}
LogicalOperator

邏輯運(yùn)算符,兩種值,即與或。

enum LogicalOperator {
    "||" | "&&"
}
MemberExpression

成員表達(dá)式節(jié)點(diǎn),即表示引用對(duì)象成員的語句,object 是引用對(duì)象的表達(dá)式節(jié)點(diǎn),property 是表示屬性名稱,computed 如果為 false,是表示 . 來引用成員,property 應(yīng)該為一個(gè) Identifier 節(jié)點(diǎn),如果 computed 屬性為 true,則是 [] 來進(jìn)行引用,即 property 是一個(gè) Expression 節(jié)點(diǎn),名稱是表達(dá)式的結(jié)果值。

interface MemberExpression <: Expression, Pattern {
    type: "MemberExpression";
    object: Expression;
    property: Expression;
    computed: boolean;
}

下邊是其他的一些表達(dá)式:

ConditionalExpression

條件表達(dá)式,通常我們稱之為三元運(yùn)算表達(dá)式,即 boolean ? true : false。屬性參考條件語句。

interface ConditionalExpression <: Expression {
    type: "ConditionalExpression";
    test: Expression;
    alternate: Expression;
    consequent: Expression;
}
CallExpression

函數(shù)調(diào)用表達(dá)式,即表示了 func(1, 2) 這一類型的語句。callee 屬性是一個(gè)表達(dá)式節(jié)點(diǎn),表示函數(shù),arguments 是一個(gè)數(shù)組,元素是表達(dá)式節(jié)點(diǎn),表示函數(shù)參數(shù)列表。

interface CallExpression <: Expression {
    type: "CallExpression";
    callee: Expression;
    arguments: [ Expression ];
}
NewExpression

new 表達(dá)式。

interface NewExpression <: CallExpression {
    type: "NewExpression";
}
SequenceExpression

這個(gè)就是逗號(hào)運(yùn)算符構(gòu)建的表達(dá)式(不知道確切的名稱),expressions 屬性為一個(gè)數(shù)組,即表示構(gòu)成整個(gè)表達(dá)式,被逗號(hào)分割的多個(gè)表達(dá)式。

interface SequenceExpression <: Expression {
    type: "SequenceExpression";
    expressions: [ Expression ];
}
Patterns

模式,主要在 ES6 的解構(gòu)賦值中有意義,在 ES5 中,可以理解為和 Identifier 差不多的東西。

interface Pattern <: Node { }

這一部分的內(nèi)容比較多,但都可以舉一反三,寫這個(gè)的時(shí)候我就當(dāng)把 JavaScript 語法再復(fù)習(xí)一遍。這個(gè)文檔還有 ES2015,ES2016,ES2017 相關(guān)的內(nèi)容,涉及的東西也蠻多,但是理解了上邊的這一些,然后從語法層面去思考這個(gè)文檔,其他的內(nèi)容也就很好理解了,這里略去,有需要請(qǐng)參閱:The Estree Spec。

Plugins

回到我們的主角,Acorn,提供了一種擴(kuò)展的方式來編寫相關(guān)的插件:Acorn Plugins。

我們可以使用插件來擴(kuò)展解析器,來解析更多的一些語法,如 .jsx 語法,有興趣的看看這個(gè)插件:acorn-jsx。

官方表示 Acorn 的插件是用于方便擴(kuò)展解析器,但是需要對(duì) Acorn 內(nèi)部的運(yùn)行極致比較了解,擴(kuò)展的方式會(huì)在原本的基礎(chǔ)上重新定義一些方法。這里不展開講了,如果我需要插件的話,會(huì)再寫文章聊聊這個(gè)東西。

Examples

現(xiàn)在我們來看一下如何應(yīng)用這個(gè)解析器,例如我們需要用來解析出一個(gè)符合 CommonJS 規(guī)范的模塊依賴了哪些模塊,我們可以用 Acorn 來解析 require 這個(gè)函數(shù)的調(diào)用,然后取出調(diào)用時(shí)的傳入?yún)?shù),便可以獲取依賴的模塊。

下邊是示例代碼:

// 遍歷所有節(jié)點(diǎn)的函數(shù)
function walkNode(node, callback) {
  callback(node)

  // 有 type 字段的我們認(rèn)為是一個(gè)節(jié)點(diǎn)
  Object.keys(node).forEach((key) => {
    const item = node[key]
    if (Array.isArray(item)) {
      item.forEach((sub) => {
        sub.type && walkNode(sub, callback)
      })
    }

    item && item.type && walkNode(item, callback)
  })
}

function parseDependencies(str) {
  const ast = acorn.parse(str, { ranges: true })
  const resource = [] // 依賴列表

  // 從根節(jié)點(diǎn)開始
  walkNode(ast, (node) => {
    const callee = node.callee
    const args = node.arguments

    // require 我們認(rèn)為是一個(gè)函數(shù)調(diào)用,并且函數(shù)名為 require,參數(shù)只有一個(gè),且必須是字面量
    if (
      node.type === "CallExpression" &&
      callee.type === "Identifier" &&
      callee.name === "require" &&
      args.length === 1 &&
      args[0].type === "Literal"
    ) {
      const args = node.arguments

      // 獲取依賴的相關(guān)信息
      resource.push({
        string: str.substring(node.range[0], node.range[1]),
        path: args[0].value,
        start: node.range[0],
        end: node.range[1]
      })
    }
  })

  return resource
}

這只是簡單的一個(gè)情況的處理,但是已經(jīng)給我們呈現(xiàn)了如何使用解析器,Webpack 則在這個(gè)的基礎(chǔ)上做了更多的東西,包括 var r = require; r("a") 或者 require.async("a") 等的處理。

AST 這個(gè)東西對(duì)于前端來說,我們無時(shí)無刻不在享受著它帶來的成果(模塊構(gòu)建,代碼壓縮,代碼混淆),所以了解一下總歸有好處。

有問題歡迎討論。

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/80880.html

相關(guān)文章

  • 高級(jí)前端基礎(chǔ)-JavaScript抽象語法樹AST

    摘要:本文主要介紹解析生成的抽象語法樹節(jié)點(diǎn),的實(shí)現(xiàn)也是基于的。原文地址解析器是把源碼轉(zhuǎn)化為抽象語法樹的解析器。參考文獻(xiàn)前端進(jìn)階之抽象語法樹抽象語法樹 前言 Babel為當(dāng)前最流行的代碼JavaScript編譯器了,其使用的JavaScript解析器為babel-parser,最初是從Acorn 項(xiàng)目fork出來的。Acorn 非常快,易于使用,并且針對(duì)非標(biāo)準(zhǔn)特性(以及那些未來的標(biāo)準(zhǔn)特性) 設(shè)...

    verano 評(píng)論0 收藏0
  • webpack源碼之a(chǎn)st簡介

    摘要:今天研究的對(duì)象是抽象語法樹它以樹狀的形式表現(xiàn)編程語言的語法結(jié)構(gòu),樹上的每個(gè)節(jié)點(diǎn)都表示源代碼中的一種結(jié)構(gòu)。總結(jié)發(fā)現(xiàn)水很深平時(shí)接觸的也比較少今天算是個(gè)入門了解下作為理解源碼前的鋪墊。參考源碼參考文檔 什么是AST 樹是一種重要的數(shù)據(jù)結(jié)構(gòu),由根結(jié)點(diǎn)和若干顆子樹構(gòu)成的。 根據(jù)結(jié)構(gòu)的不同又可以劃分為二叉樹,trie樹,紅黑樹等等。今天研究的對(duì)象是AST,抽象語法樹,它以樹狀的形式表現(xiàn)編程語言的語...

    superw 評(píng)論0 收藏0
  • 構(gòu)建工具是如何用 node 操作 html/js/css/md 文件的

    摘要:構(gòu)建工具是如何用操作文件的從本質(zhì)上來說,源代碼文件都是文本文件,文本文件的內(nèi)容都是字符串,對(duì)文本文件的操作其實(shí)就是對(duì)字符串的操作。在第二種方式中,一般也是將文本解析成一棵抽象語法樹,然后進(jìn)行操作。 構(gòu)建工具是如何用 node 操作 html/js/css/md 文件的 從本質(zhì)上來說,html/js/css/md ... 源代碼文件都是文本文件,文本文件的內(nèi)容都是字符串,對(duì)文本文件的操作...

    PingCAP 評(píng)論0 收藏0
  • 構(gòu)建工具是如何用 node 操作 html/js/css/md 文件的

    摘要:構(gòu)建工具是如何用操作文件的從本質(zhì)上來說,源代碼文件都是文本文件,文本文件的內(nèi)容都是字符串,對(duì)文本文件的操作其實(shí)就是對(duì)字符串的操作。在第二種方式中,一般也是將文本解析成一棵抽象語法樹,然后進(jìn)行操作。 構(gòu)建工具是如何用 node 操作 html/js/css/md 文件的 從本質(zhì)上來說,html/js/css/md ... 源代碼文件都是文本文件,文本文件的內(nèi)容都是字符串,對(duì)文本文件的操作...

    AZmake 評(píng)論0 收藏0
  • 在Chrome控制臺(tái)注入npm模塊

    摘要:工具和資料群高級(jí)爬蟲作者自建群,歡迎加入作者收集的爬蟲相關(guān)工具和資料正文上次研究了如何在內(nèi)置解釋器中加載模塊,這兩天手頭又有一個(gè)需求,要在瀏覽器的開發(fā)者控制臺(tái)中加載同樣的模塊,以便在控制臺(tái)中驗(yàn)證一些想法。 工具和資料 QQ群 - Javascript高級(jí)爬蟲 - 作者自建群,歡迎加入! awesome-java-crawler - 作者收集的爬蟲相關(guān)工具和資料 正文 上次研究了...

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

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

0條評(píng)論

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