精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

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

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

[[251469]] 

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

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

在線體驗:https://codepen.io/jrainlau/p...

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

接觸過小程序開發的同學應該知道,小程序運行的環境禁止new Function,eval等方法的使用,導致我們無法直接執行字符串形式的動態代碼。此外,許多平臺也對這些JS自帶的可執行動態代碼的方法進行了限制,那么我們是沒有任何辦法了嗎?既然如此,我們便可以用JS寫一個解析器,讓JS自己去運行自己。

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

二、什么是編譯器

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

比如有這么一段代碼: 

  1. const a = 1  
  2. console.log(a)  

經過編譯器處理后,它的AST長這樣:

  1.   "type""Program"
  2.   "start": 0, 
  3.   "end": 26, 
  4.   "body": [ 
  5.     { 
  6.       "type""VariableDeclaration"
  7.       "start": 0, 
  8.       "end": 11, 
  9.       "declarations": [ 
  10.         { 
  11.           "type""VariableDeclarator"
  12.           "start": 6, 
  13.           "end": 11, 
  14.           "id": { 
  15.             "type""Identifier"
  16.             "start": 6, 
  17.             "end": 7, 
  18.             "name""a" 
  19.           }, 
  20.           "init": { 
  21.             "type""Literal"
  22.             "start": 10, 
  23.             "end": 11, 
  24.             "value": 1, 
  25.             "raw""1" 
  26.           } 
  27.         } 
  28.       ], 
  29.       "kind""const" 
  30.     }, 
  31.     { 
  32.       "type""ExpressionStatement"
  33.       "start": 12, 
  34.       "end": 26, 
  35.       "expression": { 
  36.         "type""CallExpression"
  37.         "start": 12, 
  38.         "end": 26, 
  39.         "callee": { 
  40.           "type""MemberExpression"
  41.           "start": 12, 
  42.           "end": 23, 
  43.           "object": { 
  44.             "type""Identifier"
  45.             "start": 12, 
  46.             "end": 19, 
  47.             "name""console" 
  48.           }, 
  49.           "property": { 
  50.             "type""Identifier"
  51.             "start": 20, 
  52.             "end": 23, 
  53.             "name""log" 
  54.           }, 
  55.           "computed"false 
  56.         }, 
  57.         "arguments": [ 
  58.           { 
  59.             "type""Identifier"
  60.             "start": 24, 
  61.             "end": 25, 
  62.             "name""a" 
  63.           } 
  64.         ] 
  65.       } 
  66.     } 
  67.   ], 
  68.   "sourceType""module" 
  69.  

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

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

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

三、什么是解釋器

如同翻譯人員不僅能看懂一門外語,也能對其藝術加工后把它翻譯成母語一樣,人們把能夠將代碼轉化成AST的工具叫做“編譯器”,而把能夠將AST翻譯成目標語言并運行的工具叫做“解釋器”。

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

  1. 1 + 2 + 3 

當機器執行的時候,它可能會是這樣的機器碼: 

  1. 1 PUSH 1  
  2. 2 PUSH 2  
  3. ADD  
  4. 4 PUSH 3  
  5. ADD  

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

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

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

四、節點遍歷器

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

定義一個節點處理器:

  1. const nodeHandler = { 
  2.   Program () {}, 
  3.   VariableDeclaration () {}, 
  4.   ExpressionStatement () {}, 
  5.   MemberExpression () {}, 
  6.   CallExpression () {}, 
  7.   Identifier () {} 
  8.  

關于節點處理器的具體實現,會在后文進行詳細探討,這里暫時不作展開。

有了節點處理器,我們便需要去遍歷AST當中的每一個節點,遞歸地調用節點處理器,直到完成對整棵語法書的處理。

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

  1. class NodeIterator { 
  2.   constructor (node) { 
  3.     this.node = node 
  4.     this.nodeHandler = nodeHandler 
  5.   } 
  6.  
  7.   traverse (node) { 
  8.     // 根據節點類型找到節點處理器當中對應的函數 
  9.     const _eval = this.nodeHandler[node.type] 
  10.     // 若找不到則報錯 
  11.     if (!_eval) { 
  12.       throw new Error(`canjs: Unknown node type "${node.type}".`) 
  13.     } 
  14.     // 運行處理函數 
  15.     return _eval(node) 
  16.   } 
  17.  
  18.  

理論上,節點遍歷器這樣設計就可以了,但仔細推敲,發現漏了一個很重要的東西——作用域處理。

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

  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.   },  

問題在于,處理完變量聲明節點以后,理應把這個變量保存起來。按照JS語言特性,這個變量應該存放在一個作用域當中。在JS解析器的實現過程中,這個作用域可以被定義為一個scope對象。

改寫節點遍歷器,為其新增一個scope對象

  1. class NodeIterator { 
  2.   constructor (node, scope = {}) { 
  3.     this.node = node 
  4.     this.scope = scope 
  5.     this.nodeHandler = nodeHandler 
  6.   } 
  7.  
  8.   traverse (node, options = {}) { 
  9.     const scope = options.scope || this.scope 
  10.     const nodeIterator = new NodeIterator(node, scope) 
  11.     const _eval = this.nodeHandler[node.type] 
  12.     if (!_eval) { 
  13.       throw new Error(`canjs: Unknown node type "${node.type}".`) 
  14.     } 
  15.     return _eval(nodeIterator) 
  16.   } 
  17.  
  18.   createScope (blockType = 'block') { 
  19.     return new Scope(blockType, this.scope) 
  20.   } 
  21.  

然后節點處理函數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.       // 在作用域當中定義變量 
  7.       // 如果當前是塊級作用域且變量用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.   },  

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

五、作用域處理

考慮到這樣一種情況: 

  1. const a = 1 
  2.   const b = 2 
  3.   console.log(a) 
  4. console.log(b)  

運行結果必然是能夠打印出a的值,然后報錯:Uncaught ReferenceError: b is not defined

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

定義一個作用域基類Scope: 

  1. class Scope { 
  2.   constructor (type, parentScope) { 
  3.     // 作用域類型,區分函數作用域function和塊級作用域block 
  4.     this.type = type 
  5.     // 父級作用域 
  6.     this.parentScope = parentScope 
  7.     // 全局作用域 
  8.     this.globalDeclaration = standardMap 
  9.     // 當前作用域的變量空間 
  10.     this.declaration = Object.create(null
  11.   } 
  12.  
  13.   /* 
  14.    * get/set方法用于獲取/設置當前作用域中對應name的變量值 
  15.      符合JS語法規則,優先從當前作用域去找,若找不到則到父級作用域去找,然后到全局作用域找。 
  16.      如果都沒有,就報錯 
  17.    */ 
  18.   get (name) { 
  19.     if (this.declaration[name]) { 
  20.       return this.declaration[name
  21.     } else if (this.parentScope) { 
  22.       return this.parentScope.get(name
  23.     } else if (this.globalDeclaration[name]) { 
  24.       return this.globalDeclaration[name
  25.     } 
  26.     throw new ReferenceError(`${nameis not defined`) 
  27.   } 
  28.  
  29.   set (name, value) { 
  30.     if (this.declaration[name]) { 
  31.       this.declaration[name] = value 
  32.     } else if (this.parentScope[name]) { 
  33.       this.parentScope.set(name, value) 
  34.     } else { 
  35.       throw new ReferenceError(`${nameis not defined`) 
  36.     } 
  37.   } 
  38.  
  39.   /** 
  40.    * 根據變量的kind調用不同的變量定義方法 
  41.    */ 
  42.   declare (name, value, kind = 'var') { 
  43.     if (kind === 'var') { 
  44.       return this.varDeclare(name, value) 
  45.     } else if (kind === 'let') { 
  46.       return this.letDeclare(name, value) 
  47.     } else if (kind === 'const') { 
  48.       return this.constDeclare(name, value) 
  49.     } else { 
  50.       throw new Error(`canjs: Invalid Variable Declaration Kind of "${kind}"`) 
  51.     } 
  52.   } 
  53.  
  54.   varDeclare (name, value) { 
  55.     let scope = this 
  56.     // 若當前作用域存在非函數類型的父級作用域時,就把變量定義到父級作用域 
  57.     while (scope.parentScope && scope.type !== 'function') { 
  58.       scope = scope.parentScope 
  59.     } 
  60.     this.declaration[name] = new SimpleValue(value, 'var'
  61.     return this.declaration[name
  62.   } 
  63.  
  64.   letDeclare (name, value) { 
  65.     // 不允許重復定義 
  66.     if (this.declaration[name]) { 
  67.       throw new SyntaxError(`Identifier ${name} has already been declared`) 
  68.     } 
  69.     this.declaration[name] = new SimpleValue(value, 'let'
  70.     return this.declaration[name
  71.   } 
  72.  
  73.   constDeclare (name, value) { 
  74.     // 不允許重復定義 
  75.     if (this.declaration[name]) { 
  76.       throw new SyntaxError(`Identifier ${name} has already been declared`) 
  77.     } 
  78.     this.declaration[name] = new SimpleValue(value, 'const'
  79.     return this.declaration[name
  80.   } 
  81.  

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

  1. class SimpleValue { 
  2.   constructor (value, kind = '') { 
  3.     this.value = value 
  4.     this.kind = kind 
  5.   } 
  6.  
  7.   set (value) { 
  8.     // 禁止重新對const類型變量賦值 
  9.     if (this.kind === 'const') { 
  10.       throw new TypeError('Assignment to constant variable'
  11.     } else { 
  12.       this.value = value 
  13.     } 
  14.   } 
  15.  
  16.   get () { 
  17.     return this.value 
  18.   } 
  19.  

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

JS標準庫注入

細心的讀者會發現,在定義Scope基類的時候,其全局作用域globalScope被賦值了一個standardMap對象,這個對象就是JS標準庫。

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

  1. const standardMap = {  
  2. console: new SimpleValue(console)  
  3.  

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

六、節點處理器

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

在開發節點處理器之前,需要用到一個工具,用于判斷JS語句當中的return,break,continue關鍵字。

關鍵字判斷工具Signal

定義一個Signal基類:

  1. class Signal { 
  2.   constructor (type, value) { 
  3.     this.type = type 
  4.     this.value = value 
  5.   } 
  6.  
  7.   static Return (value) { 
  8.     return new Signal('return', value) 
  9.   } 
  10.  
  11.   static Break (label = null) { 
  12.     return new Signal('break', label) 
  13.   } 
  14.  
  15.   static Continue (label) { 
  16.     return new Signal('continue', label) 
  17.   } 
  18.  
  19.   static isReturn(signal) { 
  20.     return signal instanceof Signal && signal.type === 'return' 
  21.   } 
  22.  
  23.   static isContinue(signal) { 
  24.     return signal instanceof Signal && signal.type === 'continue' 
  25.   } 
  26.  
  27.   static isBreak(signal) { 
  28.     return signal instanceof Signal && signal.type === 'break' 
  29.   } 
  30.  
  31.   static isSignal (signal) { 
  32.     return signal instanceof Signal 
  33.   } 
  34.  

有了它,就可以對語句當中的關鍵字進行判斷處理,接下來會有大用處。

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

最常用的節點處理器之一,負責把變量注冊到正確的作用域。 

  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.      // 在作用域當中定義變量 
  7.      // 若為塊級作用域且關鍵字為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、標識符節點處理器——Identifier()

專門用于從作用域中獲取標識符的值。 

  1. Identifier (nodeIterator) { 
  2.     if (nodeIterator.node.name === 'undefined') { 
  3.       return undefined 
  4.     } 
  5.     return nodeIterator.scope.get(nodeIterator.node.name).value 
  6.   },  

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

返回字符節點的值。

  1. Literal (nodeIterator) { 
  2.     return nodeIterator.node.value 
  3.   }  

4、表達式調用節點處理器——CallExpression()

用于處理表達式調用節點的處理器,如處理func(),console.log()等。 

  1. CallExpression (nodeIterator) { 
  2.     // 遍歷callee獲取函數體 
  3.     const func = nodeIterator.traverse(nodeIterator.node.callee) 
  4.     // 獲取參數 
  5.     const args = nodeIterator.node.arguments.map(arg => nodeIterator.traverse(arg)) 
  6.  
  7.     let value 
  8.     if (nodeIterator.node.callee.type === 'MemberExpression') { 
  9.       value = nodeIterator.traverse(nodeIterator.node.callee.object) 
  10.     } 
  11.     // 返回函數運行結果 
  12.     return func.apply(value, args) 
  13.   },  

5、表達式節點處理器——MemberExpression()

區分于上面的“表達式調用節點處理器”,表達式節點指的是person.say,console.log這種函數表達式。

  1. MemberExpression (nodeIterator) { 
  2.     // 獲取對象,如console 
  3.     const obj = nodeIterator.traverse(nodeIterator.node.object) 
  4.     // 獲取對象的方法,如log 
  5.     const name = nodeIterator.node.property.name 
  6.     // 返回表達式,如console.log 
  7.     return obj[name
  8.   }  

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

非常常用的處理器,專門用于處理塊級聲明節點,如函數、循環、try...catch...當中的情景。

  1. BlockStatement (nodeIterator) { 
  2.    // 先定義一個塊級作用域 
  3.    let scope = nodeIterator.createScope('block'
  4.  
  5.    // 處理塊級節點內的每一個節點 
  6.    for (const node of nodeIterator.node.body) { 
  7.      if (node.type === 'VariableDeclaration' && node.kind === 'var') { 
  8.        for (const declaration of node.declarations) { 
  9.          scope.declare(declaration.id.name, declaration.init.value, node.kind) 
  10.        } 
  11.      } else if (node.type === 'FunctionDeclaration') { 
  12.        nodeIterator.traverse(node, { scope }) 
  13.      } 
  14.    } 
  15.  
  16.    // 提取關鍵字(return, break, continue) 
  17.    for (const node of nodeIterator.node.body) { 
  18.      if (node.type === 'FunctionDeclaration') { 
  19.        continue 
  20.      } 
  21.      const signal = nodeIterator.traverse(node, { scope }) 
  22.      if (Signal.isSignal(signal)) { 
  23.        return signal 
  24.      } 
  25.    } 
  26.  }  

可以看到這個處理器里面有兩個for...of循環。***個用于處理塊級內語句,第二個專門用于識別關鍵字,如循環體內部的break,continue或者函數體內部的return。

7、函數定義節點處理器——FunctionDeclaration()

往作用當中聲明一個和函數名相同的變量,值為所定義的函數:

  1. FunctionDeclaration (nodeIterator) { 
  2.     const fn = NodeHandler.FunctionExpression(nodeIterator) 
  3.     nodeIterator.scope.varDeclare(nodeIterator.node.id.name, fn) 
  4.     return fn     
  5.   }  

8、函數表達式節點處理器——FunctionExpression()

用于定義一個函數:

  1. FunctionExpression (nodeIterator) { 
  2.     const node = nodeIterator.node 
  3.     /** 
  4.      * 1、定義函數需要先為其定義一個函數作用域,且允許繼承父級作用域 
  5.      * 2、注冊`this`, `arguments`和形參到作用域的變量空間 
  6.      * 3、檢查return關鍵字 
  7.      * 4、定義函數名和長度 
  8.      */ 
  9.     const fn = function () { 
  10.       const scope = nodeIterator.createScope('function'
  11.       scope.constDeclare('this', this) 
  12.       scope.constDeclare('arguments', arguments) 
  13.  
  14.       node.params.forEach((param, index) => { 
  15.         const name = param.name 
  16.         scope.varDeclare(name, arguments[index]) 
  17.       }) 
  18.  
  19.       const signal = nodeIterator.traverse(node.body, { scope }) 
  20.       if (Signal.isReturn(signal)) { 
  21.         return signal.value 
  22.       } 
  23.     }  

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

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

  1. ThisExpression (nodeIterator) { 
  2.     const value = nodeIterator.scope.get('this'
  3.     return value ? value.value : null 
  4.   }  

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

和this表達式類似,也是直接沿用JS的語言特性,獲取函數和參數之后,通過bind關鍵字生成一個構造函數,并返回。 

  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循環節點處理器——ForStatement()

For循環的三個參數對應著節點的init,test,update屬性,對著三個屬性分別調用節點處理器處理,并放回JS原生的for循環當中即可。

  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.  
  8.     for ( 
  9.       node.init && nodeIterator.traverse(node.init, { scope }); 
  10.       node.test ? nodeIterator.traverse(node.test, { scope }) : true
  11.       node.update && nodeIterator.traverse(node.update, { scope }) 
  12.     ) { 
  13.       const signal = nodeIterator.traverse(node.body, { scope }) 
  14.        
  15.       if (Signal.isBreak(signal)) { 
  16.         break 
  17.       } else if (Signal.isContinue(signal)) { 
  18.         continue 
  19.       } else if (Signal.isReturn(signal)) { 
  20.         return signal 
  21.       } 
  22.     } 
  23.   }  

同理,for...in,while和do...while循環也是類似的處理方式,這里不再贅述。

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

處理If語句,包括if,if...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語句、三目表達式也是類似的處理方式。

---

上面列出了幾個比較重要的節點處理器,在es5當中還有很多節點需要處理,詳細內容可以訪問這個地址一探究竟。

七、定義調用方式

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

  1. const { Parser } = require('acorn'
  2. const NodeIterator = require('./iterator'
  3. const Scope = require('./scope'
  4.  
  5. class Canjs { 
  6.   constructor (code = '', extraDeclaration = {}) { 
  7.     this.code = code 
  8.     this.extraDeclaration = extraDeclaration 
  9.     this.ast = Parser.parse(code) 
  10.     this.nodeIterator = null 
  11.     this.init() 
  12.   } 
  13.  
  14.   init () { 
  15.     // 定義全局作用域,該作用域類型為函數作用域 
  16.     const globalScope = new Scope('function'
  17.     // 根據入參定義標準庫之外的全局變量 
  18.     Object.keys(this.extraDeclaration).forEach((key) => { 
  19.       globalScope.addDeclaration(key, this.extraDeclaration[key]) 
  20.     }) 
  21.     this.nodeIterator = new NodeIterator(null, globalScope) 
  22.   } 
  23.  
  24.   run () { 
  25.     return this.nodeIterator.traverse(this.ast) 
  26.   } 
  27.  

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

八、后續

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

***,這個JS解析器已經在我的Github上開源,歡迎前來交流~

https://github.com/jrainlau/c... 

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2021-04-23 16:40:49

Three.js前端代碼

2022-10-20 11:49:49

JS動畫幀,CSS

2012-08-14 10:44:52

解釋器編程

2022-06-05 13:52:32

Node.jsDNS 的原理DNS 服務器

2022-10-08 00:06:00

JS運行V8

2020-10-29 16:00:03

Node.jsweb前端

2021-06-25 10:38:05

JavaScript編譯器前端開發

2021-11-10 09:10:46

JS 錄屏功能JavaScript

2023-12-20 21:30:26

2024-05-15 10:07:11

Agents人工智能CSV

2022-03-07 09:20:00

JavaScripThree.jsNFT

2021-04-11 09:00:13

Fes.js前端

2022-07-11 22:53:59

JavaScrip編譯信小程序

2021-11-16 12:25:14

jsPPT前端

2013-03-18 10:31:22

JS異常

2023-04-10 14:20:47

ChatGPTRESTAPI

2011-06-17 10:29:04

Nodejavascript

2024-02-04 19:15:09

Nest.js管理項目

2022-03-04 14:17:08

JS工具庫錄音

2022-01-05 08:58:08

Python解釋器編程語言
點贊
收藏

51CTO技術棧公眾號

欧美精品做受xxx性少妇| 一区二区三区在线视频观看58| 2019日本中文字幕| 欧美做受高潮6| 日韩美香港a一级毛片| 亚洲色图欧美激情| 久久99精品久久久久久久久久| 国产午夜麻豆影院在线观看| 91综合网人人| 亚洲精品国偷自产在线99热 | 三级在线电影| 老司机午夜精品99久久| 欧美极品少妇xxxxⅹ免费视频| 精品无码一区二区三区| 白嫩亚洲一区二区三区| 懂色av影视一区二区三区| 黄频视频在线观看| 青青草免费在线视频| 国产专区综合网| 青青久久av北条麻妃黑人| 欧美特黄一级片| 久久超碰99| 精品国产乱码久久久久久浪潮 | 偷拍自拍在线看| 国产精品久久久久aaaa樱花| 国产原创精品| 国产又粗又猛又爽又黄的| 亚洲影视在线| 欧美极品少妇xxxxx| 人妻无码一区二区三区免费| 青青操综合网| 精品日韩在线观看| www.色就是色.com| 最新日韩三级| 香蕉加勒比综合久久| 特色特色大片在线| av中文在线| 久久美女高清视频| 精品无人乱码一区二区三区的优势| 国产老女人乱淫免费| 肉色丝袜一区二区| 欧美一级电影在线| 国产污视频在线看| 欧美日韩国产一区精品一区| www.美女亚洲精品| 成年人在线免费看片| 亚洲自拍都市欧美小说| 亚洲激情成人网| 精品无码人妻少妇久久久久久| 国产精品中文| 欧美一区二区性放荡片| 国产乱码一区二区三区四区| 国产毛片精品久久| 欧美日韩黄视频| 激情视频免费网站| 精品福利在线| 69堂成人精品免费视频| 99日在线视频| 午夜久久av| 日韩欧美一级片| 久久久久中文字幕亚洲精品| 色悠久久久久综合先锋影音下载| 日韩午夜精品电影| 国产亚洲精品成人a| 国产成人精品福利| 国产视频精品xxxx| 我和岳m愉情xxxⅹ视频| 精品不卡一区| 日韩在线视频免费观看高清中文| 国产精品视频一区二区在线观看| 亚洲情侣在线| 久久久久久久久久久久av| 国产午夜精品无码| 久久久久久黄| 国产成人精品免费久久久久| 亚洲天堂网在线视频| 国内精品伊人久久久久av影院 | 国产又猛又黄的视频| 91国内外精品自在线播放| 欧美日韩激情一区二区| 佐佐木明希电影| 天堂99x99es久久精品免费| 亚洲欧美日韩国产中文专区| 在线看片中文字幕| 女人色偷偷aa久久天堂| 欧美伊久线香蕉线新在线| 中文字幕一区二区三区四区免费看 | 一级日韩一级欧美| 国产成人av电影在线| 久久亚洲免费| 免费在线午夜视频| 亚洲一区二区视频在线| 久久久久免费精品| 青草综合视频| 亚洲国产天堂久久综合网| 九色porny自拍视频| 久久久人成影片免费观看| 国a精品视频大全| 久久精品99北条麻妃| 国产乱码精品一区二区三区五月婷| 国产一区不卡在线观看| avav免费在线观看| 性做久久久久久久免费看| 色综合色综合色综合色综合| youjizz亚洲| 中文字幕免费国产精品| 国产亚洲欧美精品久久久久久| 可以看av的网站久久看| 97超碰人人看人人| 成人免费视频| 精品久久久久久中文字幕| 亚洲一区日韩精品| 在线日韩一区| 午夜精品一区二区三区在线视| 一级黄色片视频| 91在线观看视频| 毛片av在线播放| 粉嫩av一区二区三区四区五区 | 国外色69视频在线观看| 久久久久久久久久久久国产| 石原莉奈一区二区三区在线观看| 亚洲专区国产精品| 成人高清网站| 天天综合色天天| www.亚洲自拍| 成人精品视频| 全亚洲最色的网站在线观看| a级片免费观看| 国产精品免费av| 久章草在线视频| 久久悠悠精品综合网| 欧美成人精品一区| 91资源在线视频| 欧美激情自拍偷拍| 韩国日本在线视频| 欧美一性一交| 性欧美激情精品| 亚洲精品字幕在线| 亚洲男人的天堂在线aⅴ视频| 人妻熟女一二三区夜夜爱| 91精品短视频| 九九热精品视频国产| 国产精品久久久久久免费| 国产欧美精品在线观看| 玩弄japan白嫩少妇hd| 亚洲第一福利专区| 青草青草久热精品视频在线网站| 手机在线观看毛片| 五月开心婷婷久久| 亚洲精品乱码久久| 日韩网站在线| 久久亚洲国产精品日日av夜夜| 国产欧洲在线| 精品视频中文字幕| 国产高潮久久久| 91麻豆福利精品推荐| 中国丰满人妻videoshd| 性欧美xxxx免费岛国不卡电影| 97成人精品视频在线观看| 天堂av中文字幕| 欧美日韩国产中文字幕| 成人免费av片| 日韩精品视频网| 翔田千里亚洲一二三区| 国产在视频一区二区三区吞精| 中文字幕日韩专区| 一区二区美女视频| 亚洲精品免费看| 亚洲美女在线播放| 亚洲欧美日本日韩| 日本一区二区三区免费观看| 日韩高清在线| 久久中国妇女中文字幕| www.xxx国产| 婷婷久久综合九色综合绿巨人| 少妇光屁股影院| 秋霞影院一区二区| 男插女免费视频| 国产精品99久久免费观看| 51视频国产精品一区二区| 国产高清免费av在线| 欧美日韩二区三区| 久草视频免费播放| 久久久噜噜噜久噜久久综合| 亚洲另类第一页| 在线观看视频免费一区二区三区| 欧美日韩免费精品| 久久久久毛片免费观看| 91av在线不卡| 在线观看免费版| 日韩三级中文字幕| 五月天激情国产综合婷婷婷| 国产精品久久久久天堂| 香蕉视频污视频| 蜜桃在线一区二区三区| www.av91| 色乱码一区二区三区网站| 99re视频在线观看| 欧洲av不卡| 欧美激情免费在线| 狠狠狠综合7777久夜色撩人| 欧美一区二区三区免费在线看| 国产午夜免费福利| 一区二区三区在线播放| 欧美 日韩 成人| 东方欧美亚洲色图在线| 99re精彩视频| 国产日韩一区| 欧美日韩中文字幕在线播放 | 亚洲免费视频一区| 国产精品对白久久久久粗| 国产精品九九九| 国产乱码午夜在线视频| 欧美另类极品videosbestfree| 黄色片视频在线观看| 精品国产91乱码一区二区三区| 中文在线资源天堂| 精品美女永久免费视频| 中文字幕电影av| 国产欧美视频一区二区| 久久午夜夜伦鲁鲁片| 国产酒店精品激情| 亚洲一区二区三区四区五区| 免费日韩av片| 波多野结衣家庭教师在线| 欧美日韩免费| 日本xxx免费| 天天综合网91| 天天爽天天狠久久久| 亚洲精品亚洲人成在线| 鬼打鬼之黄金道士1992林正英| 成人免费观看49www在线观看| 国产成人一区二区在线| 瑟瑟视频在线看| 久久久久久久久久av| 肉体视频在线| 欧美另类在线播放| 51xtv成人影院| 久久亚洲国产精品成人av秋霞| 91社区在线高清| 亚洲欧美日韩在线高清直播| 男人天堂亚洲二区| 日韩精品中文在线观看| 亚洲人成色777777老人头| 精品国产一区二区三区不卡 | 成人va天堂| 国产精品video| 成人视屏在线观看| 国产精品久久久久久av福利软件| 国产日韩另类视频一区| 国产精品黄色av| 成人a在线观看高清电影| 国产精品免费久久久久影院| 国产私拍福利精品视频二区| 国产成人精品国内自产拍免费看| 国产亚洲一区二区手机在线观看 | 99久久这里有精品| 91午夜在线播放| 一区二区三区自拍视频| 成人av资源网| 国产一区在线电影| 精品国产乱码久久久久久108| 久久1电影院| 免费av在线一区二区| 欧美视频免费| 综合色婷婷一区二区亚洲欧美国产| 99久久亚洲精品蜜臀| 在线视频一二三区| 亚洲视频免费| 国产精品秘入口18禁麻豆免会员| 久久香蕉精品| 在线能看的av网站| 国产精品亚洲综合一区在线观看| 亚洲成a人无码| 91麻豆福利精品推荐| 成年人看的免费视频| 亚洲女与黑人做爰| 国产成人亚洲欧洲在线| 日本精品视频一区二区| 97超碰人人模人人人爽人人爱| 欧美成人乱码一区二区三区| 天天操天天射天天舔| 在线视频日本亚洲性| 欧美v亚洲v| 国产精品美女午夜av| 日韩一二三区| 日本成人三级电影网站| 久久精品影视| 免费国产黄色网址| 麻豆国产精品视频| 少妇一级淫片免费放播放| 欧美韩国日本一区| 久久久久久久久久久久久久久久久| 日韩欧美高清视频| 99久久久无码国产精品免费| 日韩av中文字幕在线| 三区四区电影在线观看| 午夜精品久久久久久久白皮肤| 国产精品久久久久久吹潮| 动漫美女被爆操久久久| 亚洲国产合集| 精品无码av无码免费专区| 日韩电影在线观看电影| 久久性爱视频网站| 亚洲欧洲日本在线| 久久精品视频7| 欧美成人精品高清在线播放 | 久久6精品影院| 欧美成人精品三级网站| 国产精品久久久久久久久婷婷 | 久久伊人免费视频| 国产综合av| 国产私拍一区| 伊人色**天天综合婷婷| 看欧美ab黄色大片视频免费| 成人动漫一区二区| 中文字幕电影av| 欧美三级电影在线观看| 青青久在线视频免费观看| 欧美国产在线视频| av在线国产精品| 视频一区二区精品| 亚洲综合日本| 亚洲黄色小说在线观看| 亚洲人成亚洲人成在线观看图片| 奴色虐av一区二区三区| 日韩av中文字幕在线免费观看| 香蕉久久aⅴ一区二区三区| 国产欧美精品久久久| 国产一区二区三区四区二区| 内射国产内射夫妻免费频道| 成人午夜在线播放| 欧美黄色免费观看| 51精品国自产在线| 日本网站在线免费观看视频| 国产成人精品视频| 免费一区二区三区视频导航| www.中文字幕在线| 99久久综合99久久综合网站| 国产一级做a爱免费视频| 欧美成人aa大片| 男女免费观看在线爽爽爽视频| 91在线视频九色| 国产精品久久久久久影院8一贰佰 国产精品久久久久久麻豆一区软件 | 中文无字幕一区二区三区 | av人人综合网| 国产精品麻豆免费版| 亚洲手机视频| 99久久久无码国产精品性波多 | 人人妻人人爽人人澡人人精品 | 欧美日韩免费一区二区三区 | 精品久久ai| 性欧美大战久久久久久久| 成人短视频下载| 狠狠躁夜夜躁人人爽天天高潮| 日韩av一卡二卡| 91av亚洲| 亚洲 国产 日韩 综合一区| 免费精品视频在线| 羞羞在线观看视频| 日韩午夜电影av| 678在线观看视频| 欧美午夜精品久久久久久蜜| 日韩国产欧美在线视频| 欧美xxxooo| 精品久久一区二区三区| 僵尸再翻生在线观看| 日本不卡高清视频一区| 蜜臀av一区二区| www青青草原| 日韩av在线看| av成人亚洲| 久久福利一区二区| 99精品欧美一区二区三区小说| 免费精品一区二区| 久久精品久久久久| 99国产精品久久一区二区三区| 91黄色小网站| 国产精品短视频| 高清国产mv在线观看| 全亚洲最色的网站在线观看| 国产精品成久久久久| 成熟妇人a片免费看网站| 欧美性极品少妇| 日本在线视频www鲁啊鲁| 久久综合精品一区| 久久97超碰国产精品超碰| 久久久久无码精品国产| 亚洲人成电影网站| av在线精品| 欧美a在线视频| 亚洲欧美另类小说视频| 亚洲av成人无码网天堂| 国产在线精品播放| 99国产成+人+综合+亚洲欧美| 国产成人在线网址| 亚洲国产精品va在线看黑人| 国精品产品一区| 欧美三级一级片| 亚洲色大成网站www久久九九|