日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

前端與編譯原理:用 JS 寫一個 JS 解釋器

 Earlycl6i7o9ek 2019-02-24

(給前端大全加星標(biāo),提升前端技能


作者:jrainlau

https://segmentfault.com/a/1190000017241258


說起編譯原理,印象往往只停留在本科時那些枯燥的課程和晦澀的概念。作為前端開發(fā)者,編譯原理似乎離我們很遠(yuǎn),對它的理解很可能僅僅局限于“抽象語法樹(AST)”。但這僅僅是個開頭而已。編譯原理的使用,甚至能讓我們利用JS直接寫一個能運行JS代碼的解釋器。

項目地址:https://github.com/jrainlau/canjs

在線體驗:https:///jrainlau/pen/YRgQXo

一、為什么要用JS寫JS的解釋器

接觸過小程序開發(fā)的同學(xué)應(yīng)該知道,小程序運行的環(huán)境禁止 newFunction, eval等方法的使用,導(dǎo)致我們無法直接執(zhí)行字符串形式的動態(tài)代碼。此外,許多平臺也對這些JS自帶的可執(zhí)行動態(tài)代碼的方法進(jìn)行了限制,那么我們是沒有任何辦法了嗎?既然如此,我們便可以用JS寫一個解析器,讓JS自己去運行自己。

在開始之前,我們先簡單回顧一下編譯原理的一些概念。

二、什么是編譯器

說到編譯原理,肯定離不開編譯器。簡單來說,當(dāng)一段代碼經(jīng)過編譯器的詞法分析、語法分析等階段之后,會生成一個樹狀結(jié)構(gòu)的“抽象語法樹(AST)”,該語法樹的每一個節(jié)點都對應(yīng)著代碼當(dāng)中不同含義的片段。

比如有這么一段代碼:

  1. const a = 1

  2. console.log(a)

經(jīng)過編譯器處理后,它的AST長這樣:

  1. {

  2.  'type': 'Program',

  3.  'start': 0,

  4.  'end': 26,

  5.  'body': [

  6.    {

  7.      'type': 'VariableDeclaration',

  8.      'start': 0,

  9.      'end': 11,

  10.      'declarations': [

  11.        {

  12.          'type': 'VariableDeclarator',

  13.          'start': 6,

  14.          'end': 11,

  15.          'id': {

  16.            'type': 'Identifier',

  17.            'start': 6,

  18.            'end': 7,

  19.            'name': 'a'

  20.          },

  21.          'init': {

  22.            'type': 'Literal',

  23.            'start': 10,

  24.            'end': 11,

  25.            'value': 1,

  26.            'raw': '1'

  27.          }

  28.        }

  29.      ],

  30.      'kind': 'const'

  31.    },

  32.    {

  33.      'type': 'ExpressionStatement',

  34.      'start': 12,

  35.      'end': 26,

  36.      'expression': {

  37.        'type': 'CallExpression',

  38.        'start': 12,

  39.        'end': 26,

  40.        'callee': {

  41.          'type': 'MemberExpression',

  42.          'start': 12,

  43.          'end': 23,

  44.          'object': {

  45.            'type': 'Identifier',

  46.            'start': 12,

  47.            'end': 19,

  48.            'name': 'console'

  49.          },

  50.          'property': {

  51.            'type': 'Identifier',

  52.            'start': 20,

  53.            'end': 23,

  54.            'name': 'log'

  55.          },

  56.          'computed': false

  57.        },

  58.        'arguments': [

  59.          {

  60.            'type': 'Identifier',

  61.            'start': 24,

  62.            'end': 25,

  63.            'name': 'a'

  64.          }

  65.        ]

  66.      }

  67.    }

  68.  ],

  69.  'sourceType': 'module'

  70. }

常見的JS編譯器有 babylon, acorn等等,感興趣的同學(xué)可以在 AST explorer 這個網(wǎng)站自行體驗。

可以看到,編譯出來的AST詳細(xì)記錄了代碼中所有語義代碼的類型、起始位置等信息。這段代碼除了根節(jié)點 Program外,主體包含了兩個節(jié)點 VariableDeclarationExpressionStatement,而這些節(jié)點里面又包含了不同的子節(jié)點。

正是由于AST詳細(xì)記錄了代碼的語義化信息,所以Babel,Webpack,Sass,Less等工具可以針對代碼進(jìn)行非常智能的處理。

三、什么是解釋器

如同翻譯人員不僅能看懂一門外語,也能對其藝術(shù)加工后把它翻譯成母語一樣,人們把能夠?qū)⒋a轉(zhuǎn)化成AST的工具叫做“編譯器”,而把能夠?qū)ST翻譯成目標(biāo)語言并運行的工具叫做“解釋器”。

在編譯原理的課程中,我們思考過這么一個問題:如何讓計算機(jī)運行算數(shù)表達(dá)式 1 2 3

  1. 1 2 3

當(dāng)機(jī)器執(zhí)行的時候,它可能會是這樣的機(jī)器碼:

  1. 1 PUSH 1

  2. 2 PUSH 2

  3. 3 ADD

  4. 4 PUSH 3

  5. 5 ADD

而運行這段機(jī)器碼的程序,就是解釋器。

在這篇文章中,我們不會搞出機(jī)器碼這樣復(fù)雜的東西,僅僅是使用JS在其runtime環(huán)境下去解釋JS代碼的AST。由于解釋器使用JS編寫,所以我們可以大膽使用JS自身的語言特性,比如this綁定、new關(guān)鍵字等等,完全不需要對它們進(jìn)行額外處理,也因此讓JS解釋器的實現(xiàn)變得非常簡單。

在回顧了編譯原理的基本概念之后,我們就可以著手進(jìn)行開發(fā)了。

四、節(jié)點遍歷器

通過分析上文的AST,可以看到每一個節(jié)點都會有一個類型屬性 type,不同類型的節(jié)點需要不同的處理方式,處理這些節(jié)點的程序,就是“節(jié)點處理器 nodeHandler”。

定義一個節(jié)點處理器:

  1. const nodeHandler = {

  2.  Program () {},

  3.  VariableDeclaration () {},

  4.  ExpressionStatement () {},

  5.  MemberExpression () {},

  6.  CallExpression () {},

  7.  Identifier () {}

  8. }

關(guān)于節(jié)點處理器的具體實現(xiàn),會在后文進(jìn)行詳細(xì)探討,這里暫時不作展開。

有了節(jié)點處理器,我們便需要去遍歷AST當(dāng)中的每一個節(jié)點,遞歸地調(diào)用節(jié)點處理器,直到完成對整棵語法書的處理。

定義一個節(jié)點遍歷器 NodeIterator

  1. class NodeIterator {

  2.  constructor (node) {

  3.    this.node = node

  4.    this.nodeHandler = nodeHandler

  5.  }


  6.  traverse (node) {

  7.    // 根據(jù)節(jié)點類型找到節(jié)點處理器當(dāng)中對應(yīng)的函數(shù)

  8.    const _eval = this.nodeHandler[node.type]

  9.    // 若找不到則報錯

  10.    if (!_eval) {

  11.      throw new Error(`canjs: Unknown node type '${node.type}'.`)

  12.    }

  13.    // 運行處理函數(shù)

  14.    return _eval(node)

  15.  }


  16. }

理論上,節(jié)點遍歷器這樣設(shè)計就可以了,但仔細(xì)推敲,發(fā)現(xiàn)漏了一個很重要的東西——作用域處理。

回到節(jié)點處理器的 VariableDeclaration()方法,它用來處理諸如 consta=1這樣的變量聲明節(jié)點。假設(shè)它的代碼如下:

  1.  VariableDeclaration (node) {

  2.    for (const declaration of node.declarations) {

  3.      const { name } = declaration.id

  4.      const value = declaration.init ? traverse(declaration.init) : undefined

  5.      // 問題來了,拿到了變量的名稱和值,然后把它保存到哪里去呢?

  6.      // ...

  7.    }

  8.  },

問題在于,處理完變量聲明節(jié)點以后,理應(yīng)把這個變量保存起來。按照J(rèn)S語言特性,這個變量應(yīng)該存放在一個作用域當(dāng)中。在JS解析器的實現(xiàn)過程中,這個作用域可以被定義為一個 scope對象。

改寫節(jié)點遍歷器,為其新增一個 scope對象:

  1. class NodeIterator {

  2.  constructor (node, scope = {}) {

  3.    this.node = node

  4.    this.scope = scope

  5.    this.nodeHandler = nodeHandler

  6.  }


  7.  traverse (node, options = {}) {

  8.    const scope = options.scope || this.scope

  9.    const nodeIterator = new NodeIterator(node, scope)

  10.    const _eval = this.nodeHandler[node.type]

  11.    if (!_eval) {

  12.      throw new Error(`canjs: Unknown node type '${node.type}'.`)

  13.    }

  14.    return _eval(nodeIterator)

  15.  }


  16.  createScope (blockType = 'block') {

  17.    return new Scope(blockType, this.scope)

  18.  }

  19. }

然后節(jié)點處理函數(shù) VariableDeclaration()就可以通過 scope保存變量了:

  1.  VariableDeclaration (nodeIterator) {

  2.    const kind = nodeIterator.node.kind

  3.    for (const declaration of nodeIterator.node.declarations) {

  4.      const { name } = declaration.id

  5.      const value = declaration.init ? nodeIterator.traverse(declaration.init) : undefined

  6.      // 在作用域當(dāng)中定義變量

  7.      // 如果當(dāng)前是塊級作用域且變量用var定義,則定義到父級作用域

  8.      if (nodeIterator.scope.type === 'block' && kind === 'var') {

  9.        nodeIterator.scope.parentScope.declare(name, value, kind)

  10.      } else {

  11.        nodeIterator.scope.declare(name, value, kind)

  12.      }

  13.    }

  14.  },

關(guān)于作用域的處理,可以說是整個JS解釋器最難的部分。接下來我們將對作用域處理進(jìn)行深入的剖析。

五、作用域處理

考慮到這樣一種情況:

  1. const a = 1

  2. {

  3.  const b = 2

  4.  console.log(a)

  5. }

  6. console.log(b)

運行結(jié)果必然是能夠打印出 a的值,然后報錯: UncaughtReferenceError:bisnotdefined

這段代碼就是涉及到了作用域的問題。塊級作用域或者函數(shù)作用域可以讀取其父級作用域當(dāng)中的變量,反之則不行,所以對于作用域我們不能簡單地定義一個空對象,而是要專門進(jìn)行處理。

定義一個作用域基類 Scope

  1. class Scope {

  2.  constructor (type, parentScope) {

  3.    // 作用域類型,區(qū)分函數(shù)作用域function和塊級作用域block

  4.    this.type = type

  5.    // 父級作用域

  6.    this.parentScope = parentScope

  7.    // 全局作用域

  8.    this.globalDeclaration = standardMap

  9.    // 當(dāng)前作用域的變量空間

  10.    this.declaration = Object.create(null)

  11.  }


  12.  /*

  13.   * get/set方法用于獲取/設(shè)置當(dāng)前作用域中對應(yīng)name的變量值

  14.     符合JS語法規(guī)則,優(yōu)先從當(dāng)前作用域去找,若找不到則到父級作用域去找,然后到全局作用域找。

  15.     如果都沒有,就報錯

  16.   */

  17.  get (name) {

  18.    if (this.declaration[name]) {

  19.      return this.declaration[name]

  20.    } else if (this.parentScope) {

  21.      return this.parentScope.get(name)

  22.    } else if (this.globalDeclaration[name]) {

  23.      return this.globalDeclaration[name]

  24.    }

  25.    throw new ReferenceError(`${name} is not defined`)

  26.  }


  27.  set (name, value) {

  28.    if (this.declaration[name]) {

  29.      this.declaration[name] = value

  30.    } else if (this.parentScope[name]) {

  31.      this.parentScope.set(name, value)

  32.    } else {

  33.      throw new ReferenceError(`${name} is not defined`)

  34.    }

  35.  }


  36.  /**

  37.   * 根據(jù)變量的kind調(diào)用不同的變量定義方法

  38.   */

  39.  declare (name, value, kind = 'var') {

  40.    if (kind === 'var') {

  41.      return this.varDeclare(name, value)

  42.    } else if (kind === 'let') {

  43.      return this.letDeclare(name, value)

  44.    } else if (kind === 'const') {

  45.      return this.constDeclare(name, value)

  46.    } else {

  47.      throw new Error(`canjs: Invalid Variable Declaration Kind of '${kind}'`)

  48.    }

  49.  }


  50.  varDeclare (name, value) {

  51.    let scope = this

  52.    // 若當(dāng)前作用域存在非函數(shù)類型的父級作用域時,就把變量定義到父級作用域

  53.    while (scope.parentScope && scope.type !== 'function') {

  54.      scope = scope.parentScope

  55.    }

  56.    this.declaration[name] = new SimpleValue(value, 'var')

  57.    return this.declaration[name]

  58.  }


  59.  letDeclare (name, value) {

  60.    // 不允許重復(fù)定義

  61.    if (this.declaration[name]) {

  62.      throw new SyntaxError(`Identifier ${name} has already been declared`)

  63.    }

  64.    this.declaration[name] = new SimpleValue(value, 'let')

  65.    return this.declaration[name]

  66.  }


  67.  constDeclare (name, value) {

  68.    // 不允許重復(fù)定義

  69.    if (this.declaration[name]) {

  70.      throw new SyntaxError(`Identifier ${name} has already been declared`)

  71.    }

  72.    this.declaration[name] = new SimpleValue(value, 'const')

  73.    return this.declaration[name]

  74.  }

  75. }

這里使用了一個叫做 simpleValue()的函數(shù)來定義變量值,主要用于處理常量:

  1. class SimpleValue {

  2.  constructor (value, kind = '') {

  3.    this.value = value

  4.    this.kind = kind

  5.  }


  6.  set (value) {

  7.    // 禁止重新對const類型變量賦值

  8.    if (this.kind === 'const') {

  9.      throw new TypeError('Assignment to constant variable')

  10.    } else {

  11.      this.value = value

  12.    }

  13.  }


  14.  get () {

  15.    return this.value

  16.  }

  17. }

處理作用域問題思路,關(guān)鍵的地方就是在于JS語言本身尋找變量的特性——優(yōu)先當(dāng)前作用域,父作用域次之,全局作用域最后。反過來,在節(jié)點處理函數(shù) VariableDeclaration()里,如果遇到塊級作用域且關(guān)鍵字為 var,則需要把這個變量也定義到父級作用域當(dāng)中,這也就是我們常說的“全局變量污染”。

JS標(biāo)準(zhǔn)庫注入

細(xì)心的讀者會發(fā)現(xiàn),在定義 Scope基類的時候,其全局作用域 globalScope被賦值了一個 standardMap對象,這個對象就是JS標(biāo)準(zhǔn)庫。

簡單來說,JS標(biāo)準(zhǔn)庫就是JS這門語言本身所帶有的一系列方法和屬性,如常用的 setTimeout, console.log等等。為了讓解析器也能夠執(zhí)行這些方法,所以我們需要為其注入標(biāo)準(zhǔn)庫:

  1. const standardMap = {

  2.  console: new SimpleValue(console)

  3. }

這樣就相當(dāng)于往解析器的全局作用域當(dāng)中注入了 console這個對象,也就可以直接被使用了。

六、節(jié)點處理器

在處理完節(jié)點遍歷器、作用域處理的工作之后,便可以來編寫節(jié)點處理器了。顧名思義,節(jié)點處理器是專門用來處理AST節(jié)點的,上文反復(fù)提及的 VariableDeclaration()方法便是其中一個。下面將對部分關(guān)鍵的節(jié)點處理器進(jìn)行講解。

在開發(fā)節(jié)點處理器之前,需要用到一個工具,用于判斷JS語句當(dāng)中的 returnbreak, continue關(guān)鍵字。

關(guān)鍵字判斷工具 Signal

定義一個 Signal基類:

  1. class Signal {

  2.  constructor (type, value) {

  3.    this.type = type

  4.    this.value = value

  5.  }


  6.  static Return (value) {

  7.    return new Signal('return', value)

  8.  }


  9.  static Break (label = null) {

  10.    return new Signal('break', label)

  11.  }


  12.  static Continue (label) {

  13.    return new Signal('continue', label)

  14.  }


  15.  static isReturn(signal) {

  16.    return signal instanceof Signal && signal.type === 'return'

  17.  }


  18.  static isContinue(signal) {

  19.    return signal instanceof Signal && signal.type === 'continue'

  20.  }


  21.  static isBreak(signal) {

  22.    return signal instanceof Signal && signal.type === 'break'

  23.  }


  24.  static isSignal (signal) {

  25.    return signal instanceof Signal

  26.  }

  27. }

有了它,就可以對語句當(dāng)中的關(guān)鍵字進(jìn)行判斷處理,接下來會有大用處。

1、變量定義節(jié)點處理器—— VariableDeclaration()

最常用的節(jié)點處理器之一,負(fù)責(zé)把變量注冊到正確的作用域。

  1.  VariableDeclaration (nodeIterator) {

  2.    const kind = nodeIterator.node.kind

  3.    for (const declaration of nodeIterator.node.declarations) {

  4.      const { name } = declaration.id

  5.      const value = declaration.init ? nodeIterator.traverse(declaration.init) : undefined

  6.      // 在作用域當(dāng)中定義變量

  7.      // 若為塊級作用域且關(guān)鍵字為var,則需要做全局污染

  8.      if (nodeIterator.scope.type === 'block' && kind === 'var') {

  9.        nodeIterator.scope.parentScope.declare(name, value, kind)

  10.      } else {

  11.        nodeIterator.scope.declare(name, value, kind)

  12.      }

  13.    }

  14.  },

2、標(biāo)識符節(jié)點處理器—— Identifier()

專門用于從作用域中獲取標(biāo)識符的值。

  1.  Identifier (nodeIterator) {

  2.    if (nodeIterator.node.name === 'undefined') {

  3.      return undefined

  4.    }

  5.    return nodeIterator.scope.get(nodeIterator.node.name).value

  6.  },

3、字符節(jié)點處理器—— Literal()

返回字符節(jié)點的值。

  1.  Literal (nodeIterator) {

  2.    return nodeIterator.node.value

  3.  }

4、表達(dá)式調(diào)用節(jié)點處理器—— CallExpression()

用于處理表達(dá)式調(diào)用節(jié)點的處理器,如處理 func(), console.log()等。

  1.  CallExpression (nodeIterator) {

  2.    // 遍歷callee獲取函數(shù)體

  3.    const func = nodeIterator.traverse(nodeIterator.node.callee)

  4.    // 獲取參數(shù)

  5.    const args = nodeIterator.node.arguments.map(arg => nodeIterator.traverse(arg))


  6.    let value

  7.    if (nodeIterator.node.callee.type === 'MemberExpression') {

  8.      value = nodeIterator.traverse(nodeIterator.node.callee.object)

  9.    }

  10.    // 返回函數(shù)運行結(jié)果

  11.    return func.apply(value, args)

  12.  },

5、表達(dá)式節(jié)點處理器—— MemberExpression()

區(qū)分于上面的“表達(dá)式調(diào)用節(jié)點處理器”,表達(dá)式節(jié)點指的是 person.say, console.log這種函數(shù)表達(dá)式。

  1.  MemberExpression (nodeIterator) {

  2.    // 獲取對象,如console

  3.    const obj = nodeIterator.traverse(nodeIterator.node.object)

  4.    // 獲取對象的方法,如log

  5.    const name = nodeIterator.node.property.name

  6.    // 返回表達(dá)式,如console.log

  7.    return obj[name]

  8.  }

6、塊級聲明節(jié)點處理器—— BlockStatement()

非常常用的處理器,專門用于處理塊級聲明節(jié)點,如函數(shù)、循環(huán)、 try...catch...當(dāng)中的情景。

  1.  BlockStatement (nodeIterator) {

  2.    // 先定義一個塊級作用域

  3.    let scope = nodeIterator.createScope('block')


  4.    // 處理塊級節(jié)點內(nèi)的每一個節(jié)點

  5.    for (const node of nodeIterator.node.body) {

  6.      if (node.type === 'VariableDeclaration' && node.kind === 'var') {

  7.        for (const declaration of node.declarations) {

  8.          scope.declare(declaration.id.name, declaration.init.value, node.kind)

  9.        }

  10.      } else if (node.type === 'FunctionDeclaration') {

  11.        nodeIterator.traverse(node, { scope })

  12.      }

  13.    }


  14.    // 提取關(guān)鍵字(return, break, continue)

  15.    for (const node of nodeIterator.node.body) {

  16.      if (node.type === 'FunctionDeclaration') {

  17.        continue

  18.      }

  19.      const signal = nodeIterator.traverse(node, { scope })

  20.      if (Signal.isSignal(signal)) {

  21.        return signal

  22.      }

  23.    }

  24.  }

可以看到這個處理器里面有兩個 for...of循環(huán)。第一個用于處理塊級內(nèi)語句,第二個專門用于識別關(guān)鍵字,如循環(huán)體內(nèi)部的 break, continue或者函數(shù)體內(nèi)部的 return。

7、函數(shù)定義節(jié)點處理器—— FunctionDeclaration()

往作用當(dāng)中聲明一個和函數(shù)名相同的變量,值為所定義的函數(shù):

  1.  FunctionDeclaration (nodeIterator) {

  2.    const fn = NodeHandler.FunctionExpression(nodeIterator)

  3.    nodeIterator.scope.varDeclare(nodeIterator.node.id.name, fn)

  4.    return fn    

  5.  }

8、函數(shù)表達(dá)式節(jié)點處理器—— FunctionExpression()

用于定義一個函數(shù):

  1.  FunctionExpression (nodeIterator) {

  2.    const node = nodeIterator.node

  3.    /**

  4.     * 1、定義函數(shù)需要先為其定義一個函數(shù)作用域,且允許繼承父級作用域

  5.     * 2、注冊`this`, `arguments`和形參到作用域的變量空間

  6.     * 3、檢查return關(guān)鍵字

  7.     * 4、定義函數(shù)名和長度

  8.     */

  9.    const fn = function () {

  10.      const scope = nodeIterator.createScope('function')

  11.      scope.constDeclare('this', this)

  12.      scope.constDeclare('arguments', arguments)


  13.      node.params.forEach((param, index) => {

  14.        const name = param.name

  15.        scope.varDeclare(name, arguments[index])

  16.      })


  17.      const signal = nodeIterator.traverse(node.body, { scope })

  18.      if (Signal.isReturn(signal)) {

  19.        return signal.value

  20.      }

  21.    }


  22.    Object.defineProperties(fn, {

  23.      name: { value: node.id ? node.id.name : '' },

  24.      length: { value: node.params.length }

  25.    })


  26.    return fn

  27.  }

9、this表達(dá)式處理器—— ThisExpression()

該處理器直接使用JS語言自身的特性,把 this關(guān)鍵字從作用域中取出即可。

  1.  ThisExpression (nodeIterator) {

  2.    const value = nodeIterator.scope.get('this')

  3.    return value ? value.value : null

  4.  }

10、new表達(dá)式處理器—— NewExpression()

this表達(dá)式類似,也是直接沿用JS的語言特性,獲取函數(shù)和參數(shù)之后,通過 bind關(guān)鍵字生成一個構(gòu)造函數(shù),并返回。

  1.  NewExpression (nodeIterator) {

  2.    const func = nodeIterator.traverse(nodeIterator.node.callee)

  3.    const args = nodeIterator.node.arguments.map(arg => nodeIterator.traverse(arg))

  4.    return new (func.bind(null, ...args))

  5.  }

11、For循環(huán)節(jié)點處理器—— ForStatement()

For循環(huán)的三個參數(shù)對應(yīng)著節(jié)點的 inittest, update屬性,對著三個屬性分別調(diào)用節(jié)點處理器處理,并放回JS原生的for循環(huán)當(dāng)中即可。

  1.  ForStatement (nodeIterator) {

  2.    const node = nodeIterator.node

  3.    let scope = nodeIterator.scope

  4.    if (node.init && node.init.type === 'VariableDeclaration' && node.init.kind !== 'var') {

  5.      scope = nodeIterator.createScope('block')

  6.    }


  7.    for (

  8.      node.init && nodeIterator.traverse(node.init, { scope });

  9.      node.test ? nodeIterator.traverse(node.test, { scope }) : true;

  10.      node.update && nodeIterator.traverse(node.update, { scope })

  11.    ) {

  12.      const signal = nodeIterator.traverse(node.body, { scope })


  13.      if (Signal.isBreak(signal)) {

  14.        break

  15.      } else if (Signal.isContinue(signal)) {

  16.        continue

  17.      } else if (Signal.isReturn(signal)) {

  18.        return signal

  19.      }

  20.    }

  21.  }

同理, for...in, whiledo...while循環(huán)也是類似的處理方式,這里不再贅述。

12、If聲明節(jié)點處理器—— IfStatemtnt()

處理If語句,包括 ifif...else, if...elseif...else。

  1.  IfStatement (nodeIterator) {

  2.    if (nodeIterator.traverse(nodeIterator.node.test)) {

  3.      return nodeIterator.traverse(nodeIterator.node.consequent)

  4.    } else if (nodeIterator.node.alternate) {

  5.      return nodeIterator.traverse(nodeIterator.node.alternate)

  6.    }

  7.  }

同理, switch語句、三目表達(dá)式也是類似的處理方式。

上面列出了幾個比較重要的節(jié)點處理器,在es5當(dāng)中還有很多節(jié)點需要處理,詳細(xì)內(nèi)容可以訪 https://github.com/jrainlau/canjs/blob/master/src/es_versions/es5.js 一探究竟。

七、定義調(diào)用方式

經(jīng)過了上面的所有步驟,解析器已經(jīng)具備處理es5代碼的能力,接下來就是對這些散裝的內(nèi)容進(jìn)行組裝,最終定義一個方便用戶調(diào)用的辦法。

  1. const { Parser } = require('acorn')

  2. const NodeIterator = require('./iterator')

  3. const Scope = require('./scope')


  4. class Canjs {

  5.  constructor (code = '', extraDeclaration = {}) {

  6.    this.code = code

  7.    this.extraDeclaration = extraDeclaration

  8.    this.ast = Parser.parse(code)

  9.    this.nodeIterator = null

  10.    this.init()

  11.  }


  12.  init () {

  13.    // 定義全局作用域,該作用域類型為函數(shù)作用域

  14.    const globalScope = new Scope('function')

  15.    // 根據(jù)入?yún)⒍x標(biāo)準(zhǔn)庫之外的全局變量

  16.    Object.keys(this.extraDeclaration).forEach((key) => {

  17.      globalScope.addDeclaration(key, this.extraDeclaration[key])

  18.    })

  19.    this.nodeIterator = new NodeIterator(null, globalScope)

  20.  }


  21.  run () {

  22.    return this.nodeIterator.traverse(this.ast)

  23.  }

  24. }

這里我們定義了一個名為 Canjs的基類,接受字符串形式的JS代碼,同時可定義標(biāo)準(zhǔn)庫之外的變量。當(dāng)運行 run()方法的時候就可以得到運行結(jié)果。

八、后續(xù)

至此,整個JS解析器已經(jīng)完成,可以很好地運行ES5的代碼(可能還有bug沒有發(fā)現(xiàn))。但是在當(dāng)前的實現(xiàn)中,所有的運行結(jié)果都是放在一個類似沙盒的地方,無法對外界產(chǎn)生影響。如果要把運行結(jié)果取出來,可能的辦法有兩種。第一種是傳入一個全局的變量,把影響作用在這個全局變量當(dāng)中,借助它把結(jié)果帶出來;另外一種則是讓解析器支持 export語法,能夠把 export語句聲明的結(jié)果返回,感興趣的讀者可以自行研究。

最后,這個JS解析器已經(jīng)在我的Github上開源,歡迎前來交流:https://github.com/jrainlau/canjs

參考資料

  • 從零開始寫一個Javascript解析器

  • 微信小程序也要強(qiáng)行熱更代碼,鵝廠不服你來肛我呀

  • jkeylu/evil-eval

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多