<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Babel是如何讀懂JS代碼的

          共 4140字,需瀏覽 9分鐘

           ·

          2020-10-28 20:31

          編者按:本文轉(zhuǎn)載自安秦的知乎文章,快來(lái)一起學(xué)習(xí)吧!

          概述

          本文不再介紹Babel是什么也不講怎么用,這類文章很多,我也不覺(jué)得自己能寫得更好。這篇文章的關(guān)注點(diǎn)是另一個(gè)方面,也是很多人會(huì)好奇的事情,Babel的工作原理是什么。

          Babel工作的三個(gè)階段

          首先要說(shuō)明的是,現(xiàn)在前端流行用的WebPack或其他同類工程化工具會(huì)將源文件組合起來(lái),這部分并不是Babel完成的,是這些打包工具自己實(shí)現(xiàn)的,Babel的功能非常純粹,以字符串的形式將源代碼傳給它,它就會(huì)返回一段新的代碼字符串(以及sourcemap)。他既不會(huì)運(yùn)行你的代碼,也不會(huì)將多個(gè)代碼打包到一起,它就是個(gè)編譯器,輸入語(yǔ)言是ES6+,編譯目標(biāo)語(yǔ)言是ES5。

          在Babel官網(wǎng),plugins菜單下藏著一個(gè)鏈接:thejameskyle/the-super-tiny-compiler。它已經(jīng)解釋了整個(gè)工作過(guò)程,有耐心者可以自己研究,當(dāng)然也可以繼續(xù)看我的文章。

          Babel的編譯過(guò)程跟絕大多數(shù)其他語(yǔ)言的編譯器大致同理,分為三個(gè)階段:

          1. 解析:將代碼字符串解析成抽象語(yǔ)法樹(shù)

          2. 變換:對(duì)抽象語(yǔ)法樹(shù)進(jìn)行變換操作

          3. 再建:根據(jù)變換后的抽象語(yǔ)法樹(shù)再生成代碼字符串


          像我們?cè)?babelrc里配置的presets和plugins都是在第2步工作的。

          舉個(gè)例子,首先你輸入的代碼如下:

          if (1 > 0) {
          alert('hi');
          }

          經(jīng)過(guò)第1步得到一個(gè)如下的對(duì)象:

          {
          "type": "Program", // 程序根節(jié)點(diǎn)
          "body": [ // 一個(gè)數(shù)組包含所有程序的頂層語(yǔ)句
          {
          "type": "IfStatement", // 一個(gè)if語(yǔ)句節(jié)點(diǎn)
          "test": { // if語(yǔ)句的判斷條件
          "type": "BinaryExpression", // 一個(gè)雙元運(yùn)算表達(dá)式節(jié)點(diǎn)
          "operator": ">", // 運(yùn)算表達(dá)式的運(yùn)算符
          "left": { // 運(yùn)算符左側(cè)值
          "type": "Literal", // 一個(gè)常量表達(dá)式
          "value": 1 // 常量表達(dá)式的常量值
          },
          "right": { // 運(yùn)算符右側(cè)值
          "type": "Literal",
          "value": 0
          }
          },
          "consequent": { // if語(yǔ)句條件滿足時(shí)的執(zhí)行內(nèi)容
          "type": "BlockStatement", // 用{}包圍的代碼塊
          "body": [ // 代碼塊內(nèi)的語(yǔ)句數(shù)組
          {
          "type": "ExpressionStatement", // 一個(gè)表達(dá)式語(yǔ)句節(jié)點(diǎn)
          "expression": {
          "type": "CallExpression", // 一個(gè)函數(shù)調(diào)用表達(dá)式節(jié)點(diǎn)
          "callee": { // 被調(diào)用者
          "type": "Identifier", // 一個(gè)標(biāo)識(shí)符表達(dá)式節(jié)點(diǎn)
          "name": "alert"
          },
          "arguments": [ // 調(diào)用參數(shù)
          {
          "type": "Literal",
          "value": "hi"
          }
          ]
          }
          }
          ]
          },
          "alternative": null // if語(yǔ)句條件未滿足時(shí)的執(zhí)行內(nèi)容
          }
          ]
          }

          Babel實(shí)際生成的語(yǔ)法樹(shù)還會(huì)包含更多復(fù)雜信息,這里只展示比較關(guān)鍵的部分,欲了解更多關(guān)于ES語(yǔ)言抽象語(yǔ)法樹(shù)規(guī)范可閱讀:The ESTree Spec。

          用圖像更簡(jiǎn)單地表達(dá)上面的結(jié)構(gòu):



          第1步轉(zhuǎn)換的過(guò)程中可以驗(yàn)證語(yǔ)法的正確性,同時(shí)由字符串變?yōu)閷?duì)象結(jié)構(gòu)后更有利于精準(zhǔn)地分析以及進(jìn)行代碼結(jié)構(gòu)調(diào)整。

          第2步原理就很簡(jiǎn)單了,就是遍歷這個(gè)對(duì)象所描述的抽象語(yǔ)法樹(shù),遇到哪里需要做一下改變,就直接在對(duì)象上進(jìn)行操作,比如我把IfStatement給改成WhileStatement就達(dá)到了把條件判斷改成循環(huán)的效果。

          第3步也簡(jiǎn)單,遞歸遍歷這顆語(yǔ)法樹(shù),然后生成相應(yīng)的代碼,大概的實(shí)現(xiàn)邏輯如下:

          const types = {
          Program (node) {
          return node.body.map(child => generate(child));
          },
          IfStatement (node) {
          let code = `if (${generate(node.test)}) ${generate(node.consequent)}`;
          if (node.alternative) {
          code += `else ${generate(node.alternative)}`;
          }
          return code;
          },
          BlockStatement (node) {
          let code = node.body.map(child => generate(child));
          code = `{ ${code} }`;
          return code;
          },
          ......
          };
          function generate(node) {
          return types[node.type](node);
          }
          const ast = Babel.parse(...); // 將代碼解析成語(yǔ)法樹(shù)
          const generatedCode = generate(ast); // 將語(yǔ)法樹(shù)重新組合成代碼

          抽象語(yǔ)法樹(shù)是如何產(chǎn)生的

          第2、3步相信不用花多少篇幅大家自己都能理解,重點(diǎn)介紹的第一步來(lái)了。

          解析這一步又分成兩個(gè)步驟:

          1. 分詞:將整個(gè)代碼字符串分割成?語(yǔ)法單元?數(shù)組

          2. 語(yǔ)義分析:在分詞結(jié)果的基礎(chǔ)之上分析?語(yǔ)法單元之間的關(guān)系

          我們一步步講。

          分詞

          首先解釋一下什么是語(yǔ)法單元:語(yǔ)法單元是被解析語(yǔ)法當(dāng)中具備實(shí)際意義的最小單元,通俗點(diǎn)說(shuō)就是類似于自然語(yǔ)言中的詞語(yǔ)。

          看這句話“2020年奧運(yùn)會(huì)將在東京舉行”,不論詞性及主謂關(guān)系等,人第一步會(huì)把這句話拆分成:2020年、奧運(yùn)會(huì)、將、在、東京、舉行。這就是分詞:把整句話拆分成有意義的最小顆粒,這些小塊不能再被拆分,否則就失去它所能表達(dá)的意義了。

          那么回到代碼的解析當(dāng)中,JS代碼有哪些語(yǔ)法單元呢?大致有以下這些(其他語(yǔ)言也許類似但通常都有區(qū)別):

          • 空白:JS中連續(xù)的空格、換行、縮進(jìn)等這些如果不在字符串里,就沒(méi)有任何實(shí)際邏輯意義,所以把連續(xù)的空白符直接組合在一起作為一個(gè)語(yǔ)法單元。

          • 注釋:行注釋或塊注釋,雖然對(duì)于人類來(lái)說(shuō)有意義,但是對(duì)于計(jì)算機(jī)來(lái)說(shuō)知道這是個(gè)“注釋”就行了,并不關(guān)心內(nèi)容,所以直接作為一個(gè)不可再拆的語(yǔ)法單元

          • 字符串:對(duì)于機(jī)器而言,字符串的內(nèi)容只是會(huì)參與計(jì)算或展示,里面再細(xì)分的內(nèi)容也是沒(méi)必要分析的

          • 數(shù)字:JS語(yǔ)言里就有16、10、8進(jìn)制以及科學(xué)表達(dá)法等數(shù)字表達(dá)語(yǔ)法,數(shù)字也是個(gè)具備含義的最小單元

          • 標(biāo)識(shí)符:沒(méi)有被引號(hào)擴(kuò)起來(lái)的連續(xù)字符,可包含字母、_、$、及數(shù)字(數(shù)字不能作為開(kāi)頭)。標(biāo)識(shí)符可能代表一個(gè)變量,或者true、false這種內(nèi)置常量、也可能是if、return、function這種關(guān)鍵字,是哪種語(yǔ)義,分詞階段并不在乎,只要正確切分就好了。

          • 運(yùn)算符:+、-、*、/、>、<等等

          • 括號(hào):(...)可能表示運(yùn)算優(yōu)先級(jí)、也可能表示函數(shù)調(diào)用,分詞階段并不關(guān)注是哪種語(yǔ)義,只把“(”或“)”當(dāng)做一種基本語(yǔ)法單元

          • 還有其他:如中括號(hào)、大括號(hào)、分號(hào)、冒號(hào)、點(diǎn)等等不再一一列舉

          分詞的過(guò)過(guò)程從邏輯來(lái)講并不難解釋,但是這是個(gè)精細(xì)活,要考慮清楚所有的情況。還是以一個(gè)代碼為例:

          if (1 > 0) {
          alert("if \"1 > 0\"");
          }

          我們希望得到的分詞是:

          'if'     ' '       '('    '1'      ' '    '>'    ' '    ')'    ' '    '{'
          '\n ' 'alert' '(' '"if \"1 > 0\""' ')' ';' '\n' '}'

          注意其中"if \"1 > 0\""是作為一個(gè)語(yǔ)法單元存在,沒(méi)有再查分成if、1、>、0這樣,而且其中的轉(zhuǎn)譯符會(huì)阻止字符串早結(jié)束。

          這拆分過(guò)程其實(shí)沒(méi)啥可取巧的,就是簡(jiǎn)單粗暴地一個(gè)字符一個(gè)字符地遍歷,然后分情況討論,整個(gè)實(shí)現(xiàn)方法就是順序遍歷和大量的條件判斷。我用一個(gè)簡(jiǎn)單的實(shí)現(xiàn)來(lái)解釋,在關(guān)鍵的地方注釋,我們只考慮上面那段代碼里存在的語(yǔ)法單元類型。

          function tokenizeCode (code) {
          const tokens = []; // 結(jié)果數(shù)組
          for (let i = 0; i < code.length; i++) {
          // 從0開(kāi)始,一個(gè)字符一個(gè)字符地讀取
          let currentChar = code.charAt(i);

          if (currentChar === ';') {
          // 對(duì)于這種只有一個(gè)字符的語(yǔ)法單元,直接加到結(jié)果當(dāng)中
          tokens.push({
          type: 'sep',
          value: ';',
          });
          // 該字符已經(jīng)得到解析,不需要做后續(xù)判斷,直接開(kāi)始下一個(gè)
          continue;
          }

          if (currentChar === '(' || currentChar === ')') {
          // 與 ; 類似只是語(yǔ)法單元類型不同
          tokens.push({
          type: 'parens',
          value: currentChar,
          });
          continue;
          }

          if (currentChar === '}' || currentChar === '{') {
          // 與 ; 類似只是語(yǔ)法單元類型不同
          tokens.push({
          type: 'brace',
          value: currentChar,
          });
          continue;
          }

          if (currentChar === '>' || currentChar === '<') {
          // 與 ; 類似只是語(yǔ)法單元類型不同
          tokens.push({
          type: 'operator',
          value: currentChar,
          });
          continue;
          }

          if (currentChar === '"' || currentChar === '\'') {
          // 引號(hào)表示一個(gè)字符傳的開(kāi)始
          const token = {
          type: 'string',
          value: currentChar, // 記錄這個(gè)語(yǔ)法單元目前的內(nèi)容
          };
          tokens.push(token);

          const closer = currentChar;
          let escaped = false; // 表示下一個(gè)字符是不是被轉(zhuǎn)譯的

          // 進(jìn)行嵌套循環(huán)遍歷,尋找字符串結(jié)尾
          for (i++; i < code.length; i++) {
          currentChar = code.charAt(i);
          // 先將當(dāng)前遍歷到的字符無(wú)條件加到字符串的內(nèi)容當(dāng)中
          token.value += currentChar;
          if (escaped) {
          // 如果當(dāng)前轉(zhuǎn)譯狀態(tài)是true,就將改為false,然后就不特殊處理這個(gè)字符
          escaped = false;
          } else if (currentChar === '\\') {
          // 如果當(dāng)前字符是 \ ,將轉(zhuǎn)譯狀態(tài)設(shè)為true,下一個(gè)字符不會(huì)被特殊處理
          escaped = true;
          } else if (currentChar === closer) {
          break;
          }
          }
          continue;
          }

          if (/[0-9]/.test(currentChar)) {
          // 數(shù)字是以0到9的字符開(kāi)始的
          const token = {
          type: 'number',
          value: currentChar,
          };
          tokens.push(token);

          for (i++; i < code.length; i++) {
          currentChar = code.charAt(i);
          if (/[0-9\.]/.test(currentChar)) {
          // 如果遍歷到的字符還是數(shù)字的一部分(0到9或小數(shù)點(diǎn))
          // 這里暫不考慮會(huì)出現(xiàn)多個(gè)小數(shù)點(diǎn)以及其他進(jìn)制的情況
          token.value += currentChar;
          } else {
          // 遇到不是數(shù)字的字符就退出,需要把 i 往回調(diào),
          // 因?yàn)楫?dāng)前的字符并不屬于數(shù)字的一部分,需要做后續(xù)解析
          i--;
          break;
          }
          }
          continue;
          }

          if (/[a-zA-Z\$\_]/.test(currentChar)) {
          // 標(biāo)識(shí)符是以字母、$、_開(kāi)始的
          const token = {
          type: 'identifier',
          value: currentChar,
          };
          tokens.push(token);

          // 與數(shù)字同理
          for (i++; i < code.length; i++) {
          currentChar = code.charAt(i);
          if (/[a-zA-Z0-9\$\_]/.test(currentChar)) {
          token.value += currentChar;
          } else {
          i--;
          break;
          }
          }
          continue;
          }

          if (/\s/.test(currentChar)) {
          // 連續(xù)的空白字符組合到一起
          const token = {
          type: 'whitespace',
          value: currentChar,
          };
          tokens.push(token);

          // 與數(shù)字同理
          for (i++; i < code.length; i++) {
          currentChar = code.charAt(i);
          if (/\s]/.test(currentChar)) {
          token.value += currentChar;
          } else {
          i--;
          break;
          }
          }
          continue;
          }

          // 還可以有更多的判斷來(lái)解析其他類型的語(yǔ)法單元

          // 遇到其他情況就拋出異常表示無(wú)法理解遇到的字符
          throw new Error('Unexpected ' + currentChar);
          }
          return tokens;
          }

          const tokens = tokenizeCode(`
          if (1 > 0) {
          alert("if 1 > 0");
          }
          `);

          以上代碼是我個(gè)人的實(shí)現(xiàn)方式,與babel實(shí)際略有不同,但主要思路一樣。

          執(zhí)行結(jié)果如下:

          [
          { type: "whitespace", value: "\n" },
          { type: "identifier", value: "if" },
          { type: "whitespace", value: " " },
          { type: "parens", value: "(" },
          { type: "number", value: "1" },
          { type: "whitespace", value: " " },
          { type: "operator", value: ">" },
          { type: "whitespace", value: " " },
          { type: "number", value: "0" },
          { type: "parens", value: ")" },
          { type: "whitespace", value: " " },
          { type: "brace", value: "{" },
          { type: "whitespace", value: "\n " },
          { type: "identifier", value: "alert" },
          { type: "parens", value: "(" },
          { type: "string", value: "\"if 1 > 0\"" },
          { type: "parens", value: ")" },
          { type: "sep", value: ";" },
          { type: "whitespace", value: "\n" },
          { type: "brace", value: "}" },
          { type: "whitespace", value: "\n" },
          ]

          經(jīng)過(guò)這一步的分詞,這個(gè)數(shù)組就比攤開(kāi)的字符串更方便進(jìn)行下一步處理了。

          語(yǔ)義分析

          語(yǔ)義分析就是把詞匯進(jìn)行立體的組合,確定有多重意義的詞語(yǔ)最終是什么意思、多個(gè)詞語(yǔ)之間有什么關(guān)系以及又應(yīng)該再哪里斷句等。

          在編程語(yǔ)言解釋當(dāng)中,這就是要最終生成語(yǔ)法樹(shù)的步驟了。不像自然語(yǔ)言,像“從句”這種結(jié)構(gòu)往往最多只有一層,編程語(yǔ)言的各種從屬關(guān)系更加復(fù)雜。

          在編程語(yǔ)言的解析中有兩個(gè)很相似但是又有區(qū)別的重要概念:

          • 語(yǔ)句:語(yǔ)句是一個(gè)具備邊界的代碼區(qū)域,相鄰的兩個(gè)語(yǔ)句之間從語(yǔ)法上來(lái)講互不干擾,調(diào)換順序雖然可能會(huì)影響執(zhí)行結(jié)果,但不會(huì)產(chǎn)生語(yǔ)法錯(cuò)誤
            比如return true、var a = 10、if (...) {...}

          • 表達(dá)式:最終有個(gè)結(jié)果的一小段代碼,它的特點(diǎn)是可以原樣嵌入到另一個(gè)表達(dá)式
            比如myVar、1+1、str.replace('a', 'b')、i < 10 && i > 0等

          很多情況下一個(gè)語(yǔ)句可能只包含一個(gè)表達(dá)式,比如console.log('hi');。estree標(biāo)準(zhǔn)當(dāng)中,這種語(yǔ)句節(jié)點(diǎn)稱作ExpressionStatement。

          語(yǔ)義分析的過(guò)程又是個(gè)遍歷語(yǔ)法單元的過(guò)程,不過(guò)相比較而言更復(fù)雜,因?yàn)榉衷~過(guò)程中,每個(gè)語(yǔ)法單元都是獨(dú)立平鋪的,而語(yǔ)法分析中,語(yǔ)句和表達(dá)式會(huì)以樹(shù)狀的結(jié)構(gòu)互相包含。針對(duì)這種情況我們可以用棧,也可以用遞歸來(lái)實(shí)現(xiàn)。

          我繼續(xù)上面的例子給出語(yǔ)義分析的代碼,代碼很長(zhǎng),先在最開(kāi)頭說(shuō)明幾個(gè)函數(shù)是做什么的:

          • nextStatement:讀取并返回下一個(gè)語(yǔ)句

          • nextExpression:讀取并返回下一個(gè)表達(dá)式

          • nextToken:讀取下一個(gè)語(yǔ)法單元(或稱符號(hào)),賦值給curToken

          • stash:暫存當(dāng)前讀取符號(hào)的位置,方便在需要的時(shí)候返回

          • rewind:返回到上一個(gè)暫存點(diǎn)

          • commit:上一個(gè)暫存點(diǎn)不再被需要,將其銷毀

          這里stash、rewind、commit都跟讀取位置暫存相關(guān),什么樣的情況會(huì)需要返回到暫存點(diǎn)呢?有時(shí)同一種語(yǔ)法單元有可能代表不同類型的表達(dá)式的開(kāi)始。先stash,然后按照其中一種嘗試解析,如果解析成功了,那么暫存點(diǎn)就沒(méi)用了,commit將其銷毀。如果解析失敗了,就用rewind回到原來(lái)的位置再按照另一種方式嘗試去解析。

          以下是代碼:


          function parse (tokens) {
          let i = -1; // 用于標(biāo)識(shí)當(dāng)前遍歷位置
          let curToken; // 用于記錄當(dāng)前符號(hào)

          // 讀取下一個(gè)語(yǔ)句
          function nextStatement () {
          // 暫存當(dāng)前的i,如果無(wú)法找到符合條件的情況會(huì)需要回到這里
          stash();

          // 讀取下一個(gè)符號(hào)
          nextToken();

          if (curToken.type === 'identifier' && curToken.value === 'if') {
          // 解析 if 語(yǔ)句
          const statement = {
          type: 'IfStatement',
          };
          // if 后面必須緊跟著 (
          nextToken();
          if (curToken.type !== 'parens' || curToken.value !== '(') {
          throw new Error('Expected ( after if');
          }

          // 后續(xù)的一個(gè)表達(dá)式是 if 的判斷條件
          statement.test = nextExpression();

          // 判斷條件之后必須是 )
          nextToken();
          if (curToken.type !== 'parens' || curToken.value !== ')') {
          throw new Error('Expected ) after if test expression');
          }

          // 下一個(gè)語(yǔ)句是 if 成立時(shí)執(zhí)行的語(yǔ)句
          statement.consequent = nextStatement();

          // 如果下一個(gè)符號(hào)是 else 就說(shuō)明還存在 if 不成立時(shí)的邏輯
          if (curToken === 'identifier' && curToken.value === 'else') {
          statement.alternative = nextStatement();
          } else {
          statement.alternative = null;
          }
          commit();
          return statement;
          }

          if (curToken.type === 'brace' && curToken.value === '{') {
          // 以 { 開(kāi)頭表示是個(gè)代碼塊,我們暫不考慮JSON語(yǔ)法的存在
          const statement = {
          type: 'BlockStatement',
          body: [],
          };
          while (i < tokens.length) {
          // 檢查下一個(gè)符號(hào)是不是 }
          stash();
          nextToken();
          if (curToken.type === 'brace' && curToken.value === '}') {
          // } 表示代碼塊的結(jié)尾
          commit();
          break;
          }
          // 還原到原來(lái)的位置,并將解析的下一個(gè)語(yǔ)句加到body
          rewind();
          statement.body.push(nextStatement());
          }
          // 代碼塊語(yǔ)句解析完畢,返回結(jié)果
          commit();
          return statement;
          }

          // 沒(méi)有找到特別的語(yǔ)句標(biāo)志,回到語(yǔ)句開(kāi)頭
          rewind();

          // 嘗試解析單表達(dá)式語(yǔ)句
          const statement = {
          type: 'ExpressionStatement',
          expression: nextExpression(),
          };
          if (statement.expression) {
          nextToken();
          if (curToken.type !== 'EOF' && curToken.type !== 'sep') {
          throw new Error('Missing ; at end of expression');
          }
          return statement;
          }
          }

          // 讀取下一個(gè)表達(dá)式
          function nextExpression () {
          nextToken();

          if (curToken.type === 'identifier') {
          const identifier = {
          type: 'Identifier',
          name: curToken.value,
          };
          stash();
          nextToken();
          if (curToken.type === 'parens' && curToken.value === '(') {
          // 如果一個(gè)標(biāo)識(shí)符后面緊跟著 ( ,說(shuō)明是個(gè)函數(shù)調(diào)用表達(dá)式
          const expr = {
          type: 'CallExpression',
          caller: identifier,
          arguments: [],
          };

          stash();
          nextToken();
          if (curToken.type === 'parens' && curToken.value === ')') {
          // 如果下一個(gè)符合直接就是 ) ,說(shuō)明沒(méi)有參數(shù)
          commit();
          } else {
          // 讀取函數(shù)調(diào)用參數(shù)
          rewind();
          while (i < tokens.length) {
          // 將下一個(gè)表達(dá)式加到arguments當(dāng)中
          expr.arguments.push(nextExpression());
          nextToken();
          // 遇到 ) 結(jié)束
          if (curToken.type === 'parens' && curToken.value === ')') {
          break;
          }
          // 參數(shù)間必須以 , 相間隔
          if (curToken.type !== 'comma' && curToken.value !== ',') {
          throw new Error('Expected , between arguments');
          }
          }
          }
          commit();
          return expr;
          }
          rewind();
          return identifier;
          }

          if (curToken.type === 'number' || curToken.type === 'string') {
          // 數(shù)字或字符串,說(shuō)明此處是個(gè)常量表達(dá)式
          const literal = {
          type: 'Literal',
          value: eval(curToken.value),
          };
          // 但如果下一個(gè)符號(hào)是運(yùn)算符,那么這就是個(gè)雙元運(yùn)算表達(dá)式
          // 此處暫不考慮多個(gè)運(yùn)算銜接,或者有變量存在
          stash();
          nextToken();
          if (curToken.type === 'operator') {
          commit();
          return {
          type: 'BinaryExpression',
          left: literal,
          right: nextExpression(),
          };
          }
          rewind();
          return literal;
          }

          if (curToken.type !== 'EOF') {
          throw new Error('Unexpected token ' + curToken.value);
          }
          }

          // 往后移動(dòng)讀取指針,自動(dòng)跳過(guò)空白
          function nextToken () {
          do {
          i++;
          curToken = tokens[i] || { type: 'EOF' };
          } while (curToken.type === 'whitespace');
          }

          // 位置暫存棧,用于支持很多時(shí)候需要返回到某個(gè)之前的位置
          const stashStack = [];

          function stash (cb) {
          // 暫存當(dāng)前位置
          stashStack.push(i);
          }

          function rewind () {
          // 解析失敗,回到上一個(gè)暫存的位置
          i = stashStack.pop();
          curToken = tokens[i];
          }

          function commit () {
          // 解析成功,不需要再返回
          stashStack.pop();
          }

          const ast = {
          type: 'Program',
          body: [],
          };

          // 逐條解析頂層語(yǔ)句
          while (i < tokens.length) {
          const statement = nextStatement();
          if (!statement) {
          break;
          }
          ast.body.push(statement);
          }
          return ast;
          }

          const ast = parse([
          { type: "whitespace", value: "\n" },
          { type: "identifier", value: "if" },
          { type: "whitespace", value: " " },
          { type: "parens", value: "(" },
          { type: "number", value: "1" },
          { type: "whitespace", value: " " },
          { type: "operator", value: ">" },
          { type: "whitespace", value: " " },
          { type: "number", value: "0" },
          { type: "parens", value: ")" },
          { type: "whitespace", value: " " },
          { type: "brace", value: "{" },
          { type: "whitespace", value: "\n " },
          { type: "identifier", value: "alert" },
          { type: "parens", value: "(" },
          { type: "string", value: "\"if 1 > 0\"" },
          { type: "parens", value: ")" },
          { type: "sep", value: ";" },
          { type: "whitespace", value: "\n" },
          { type: "brace", value: "}" },
          { type: "whitespace", value: "\n" },
          ]);

          最終得到結(jié)果:

          {
          "type": "Program",
          "body": [
          {
          "type": "IfStatement",
          "test": {
          "type": "BinaryExpression",
          "left": {
          "type": "Literal",
          "value": 1
          },
          "right": {
          "type": "Literal",
          "value": 0
          }
          },
          "consequent": {
          "type": "BlockStatement",
          "body": [
          {
          "type": "ExpressionStatement",
          "expression": {
          "type": "CallExpression",
          "caller": {
          "type": "Identifier",
          "value": "alert"
          },
          "arguments": [
          {
          "type": "Literal",
          "value": "if 1 > 0"
          }
          ]
          }
          }
          ]
          },
          "alternative": null
          }
          ]
          }

          以上就是語(yǔ)義解析的部分主要思路。注意現(xiàn)在的nextExpression已經(jīng)頗為復(fù)雜,但實(shí)際實(shí)現(xiàn)要比現(xiàn)在這里展示的要更復(fù)雜很多,因?yàn)檫@里根本沒(méi)有考慮單元運(yùn)算符、運(yùn)算優(yōu)先級(jí)等等。

          結(jié)語(yǔ)

          真正看下來(lái),其實(shí)沒(méi)有哪個(gè)地方的原理特別高深莫測(cè),就是精細(xì)活,需要考慮到各種各樣的情況??傊鲆粋€(gè)完整的語(yǔ)法解釋器需要的是十分的細(xì)心與耐心。

          在并不是特別遠(yuǎn)的過(guò)去,做web項(xiàng)目,前端技術(shù)都還很簡(jiǎn)單,甚至那時(shí)候的網(wǎng)頁(yè)都盡量不用JavaScript。之后jQuery的誕生真正地讓JS成為了web應(yīng)用開(kāi)發(fā)核心,web前端工程師這種職業(yè)也才真正獨(dú)立出來(lái)。但后來(lái)隨著語(yǔ)言預(yù)處理和打包等技術(shù)的出現(xiàn),前端真的是越來(lái)越強(qiáng)大但是技術(shù)棧也真的是變得越來(lái)越復(fù)雜。雖然有種永遠(yuǎn)都學(xué)不完的感覺(jué),但這更能體現(xiàn)出我們前端工程存在的價(jià)值,不是嗎?

          ??愛(ài)心三連擊

          1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的點(diǎn)贊在看是我創(chuàng)作的動(dòng)力。

          2.關(guān)注公眾號(hào)程序員成長(zhǎng)指北,回復(fù)「1」加入Node進(jìn)階交流群!「在這里有好多 Node 開(kāi)發(fā)者,會(huì)討論 Node 知識(shí),互相學(xué)習(xí)」!

          3.也可添加微信【ikoala520】,一起成長(zhǎng)。

          “在看轉(zhuǎn)發(fā)”是最大的支持

          瀏覽 38
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩AV免费看 | 日韩中文字幕区 | 一本大道中文字幕无码29 | 18禁日韩无码 | 先锋影音男人在线资源站 |