<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>

          「 giao-js 」用js寫一個js解釋器

          共 15225字,需瀏覽 31分鐘

           ·

          2020-11-25 21:14



          作者:null仔

          來源:SegmentFault 思否社區(qū)




          前言


          在這篇文章中,我們將通過 JS 構(gòu)建我們自己的 JS 解釋器,用 JS 寫 JS,這聽起來很奇怪,盡管如此,這樣做我們將更熟悉 JS,也可以學(xué)習(xí) JS 引擎是如何工作的!




          什么是解釋器 (Interpreter) ?


          解釋器是在運(yùn)行時運(yùn)行的語言求值器,它動態(tài)地執(zhí)行程序的源代碼。
          解釋器解析源代碼,從源代碼生成 AST(抽象語法樹),遍歷 AST 并逐個計(jì)算它們。




          解釋器 (Interpreter) 工作原理



          • 詞法分析 (Tokenization)
          • 語法解析 (Parsing)
          • 求值 (Evaluating)



          詞法分析 (Tokenization)


          將源代碼分解并組織成一組有意義的單詞,這一過程即為詞法分析(Token)。
          在英語中,當(dāng)我們遇到這樣一個語句時:

          Javascript?is?the?best?language?in?the?world

          我們會下意識地把句子分解成一個個單詞:

          +----------------------------------------------------------+
          |?Javascript?|?is?|?the?|?best?|?language?|?in?|the?|world?|
          +----------------------------------------------------------+

          這是分析和理解句子的第一階段。

          詞法分析是由詞法分析器完成的,詞法分析器會掃描(scanning)代碼,提取詞法單元。

          var?a?=?1;

          [
          ??("var":?"keyword"),
          ??("a":?"identifier"),
          ??("=":?"assignment"),
          ??("1":?"literal"),
          ??(";":?"separator"),
          ];

          詞法分析器將代碼分解成 Token 后,會將 Token 傳遞給解析器進(jìn)行解析,我們來看下解析階段是如何工作的。

          語法解析 (Parsing)


          將詞法分析階段生成的 Token 轉(zhuǎn)換為抽象語法樹(Abstract Syntax Tree),這一過程稱之為語法解析(Parsing)。

          在英語中,Javascript is the best language 被分解為以下單詞:

          +------------------------------------------+
          |?Javascript?|?is?|?the?|?best?|?language??|
          +------------------------------------------+

          這樣我們就可以挑選單詞并形成語法結(jié)構(gòu):

          "Javascript":?Subject
          "is?the?best?language":?Predicate
          "language":?Object

          Javascript 在語法中是一個主語名詞,其余的是一個沒有什么意義的句子叫做謂語,language 是動作的接受者,也就是賓語。結(jié)構(gòu)是這樣的:

          Subject(Noun)?->?Predicate?->?Object

          語法解析是由語法解析器完成的,它會將上一步生成的 Token,根據(jù)語法規(guī)則,轉(zhuǎn)為抽象語法樹(AST)。

          {
          ??type:?"Program",
          ??body:?[
          ????{
          ??????type:?"VariableDeclaration",
          ??????declarations:?[
          ????????{
          ??????????type:?"VariableDeclarator",
          ??????????id:?{
          ????????????type:?"Identifier",
          ????????????name:?"sum"
          ??????????},
          ??????????init:?{
          ????????????type:?"Literal",
          ????????????value:?30,
          ????????????raw:?"30"
          ??????????}
          ????????}
          ??????],
          ??????kind:?"var"
          ????}
          ??],
          }

          求值階段 (Evaluating)


          解釋器將遍歷 AST 并計(jì)算每個節(jié)點(diǎn)。- 求值階段

          1?+?2
          |
          ????|
          ????v
          +---+??+---+
          |?1?|??|?2?|
          +---+??+---+
          ??\?????/
          ???\???/
          ????\?/
          ???+---+
          ???|?+?|
          ???+---+
          {
          ????lhs:?1,
          ????op:?'+'.
          ????rhs:?2
          }

          解釋器解析 Ast,得到 LHS 節(jié)點(diǎn),接著收集到操作符(operator)節(jié)點(diǎn)+,+操作符表示需要進(jìn)行一次加法操作,它必須有第二個節(jié)點(diǎn)來進(jìn)行加法操作.接著他收集到 RHS 節(jié)點(diǎn)。它收集到了有價值的信息并執(zhí)行加法得到了結(jié)果,3。

          {
          ??type:?"Program",
          ??body:?[
          ????{
          ??????type:?"ExpressionStatement",
          ??????expression:?{
          ????????type:?"BinaryExpression",
          ????????left:?{
          ??????????type:?"Literal",
          ??????????value:?1,
          ??????????raw:?"1"
          ????????},
          ????????operator:?"+",
          ????????right:?{
          ??????????type:?"Literal",
          ??????????value:?2,
          ??????????raw:?"2"
          ????????}
          ??????}
          ????}
          ??],
          }



          實(shí)踐


          前面我們已經(jīng)介紹了解釋器的工作原理,接下來我們來動動手松松筋骨吧,實(shí)現(xiàn)一個 Mini Js Interpreter~

          實(shí)踐準(zhǔn)備


          • Acorn.js

          A tiny, fast JavaScript parser, written completely in JavaScript. 一個完全使用 javascript 實(shí)現(xiàn)的,小型且快速的 javascript 解析器

          本次實(shí)踐我們將使用 acorn.js ,它會幫我們進(jìn)行詞法分析,語法解析并轉(zhuǎn)換為抽象語法樹。

          Webpack/Rollup/Babel(@babel/parser) 等第三方庫也是使用 acorn.js 作為自己 Parser 的基礎(chǔ)庫。(站在巨人的肩膀上啊!)

          • The Estree Spec

          最開始 Mozilla JS Parser API 是 Mozilla 工程師在 Firefox 中創(chuàng)建的 SpiderMonkey 引擎輸出 JavaScript AST 的規(guī)范文檔,文檔所描述的格式被用作操作 JAvaScript 源代碼的通用語言。

          隨著 JavaScript 的發(fā)展,更多新的語法被加入,為了幫助發(fā)展這種格式以跟上 JavaScript 語言的發(fā)展。The ESTree Spec 就誕生了,作為參與構(gòu)建和使用這些工具的人員的社區(qū)標(biāo)準(zhǔn)。

          acorn.js parse 返回值符合 ESTree spec 描述的 AST 對象,這里我們使用@types/estree 做類型定義。

          • Jest

          號稱令人愉快的 JavaScript 測試...我們使用它來進(jìn)行單元測試.

          • Rollup

          Rollup 是一個 JavaScript 模塊打包器,我們使用它來打包,以 UMD 規(guī)范對外暴露模塊。

          項(xiàng)目初始化


          // visitor.ts 創(chuàng)建一個Visitor類,并提供一個方法操作ES節(jié)點(diǎn)。
          import?*?as?ESTree?from?"estree";
          class?Visitor?{
          ??visitNode(node:?ESTree.Node)?{
          ????//?...
          ??}
          }
          export?default?Visitor;
          // interpreter.ts 創(chuàng)建一個Interpreter類,用于運(yùn)行ES節(jié)點(diǎn)樹。
          //?創(chuàng)建一個Visitor實(shí)例,并使用該實(shí)例來運(yùn)行ESTree節(jié)點(diǎn)
          import?Visitor?from?"./visitor";
          import?*?as?ESTree?from?"estree";
          class?Interpreter?{
          ??private?visitor:?Visitor;
          ??constructor(visitor:?Visitor)?{
          ????this.visitor?=?visitor;
          ??}
          ??interpret(node:?ESTree.Node)?{
          ????this.visitor.visitNode(node);
          ??}
          }
          export?default?Interpreter;
          // vm.ts 對外暴露run方法,并使用acorn code->ast后,交給Interpreter實(shí)例進(jìn)行解釋。
          const?acorn?=?require("acorn");
          import?Visitor?from?"./visitor";
          import?Interpreter?from?"./interpreter";

          const?jsInterpreter?=?new?Interpreter(new?Visitor());

          export?function?run(code:?string)?{
          ??const?root?=?acorn.parse(code,?{
          ????ecmaVersion:?8,
          ????sourceType:?"script",
          ??});
          ??return?jsInterpreter.interpret(root);
          }

          實(shí)踐第 1 彈: 1+1= ?


          我們這節(jié)來實(shí)現(xiàn) 1+1 加法的解釋。首先我們通過AST explorer,看看 1+1 這段代碼轉(zhuǎn)換后的 AST 結(jié)構(gòu)。


          我們可以看到這段代碼中存在 4 種節(jié)點(diǎn)類型,下面我們簡單的介紹一下它們:

          Program


          根節(jié)點(diǎn),即代表一整顆抽象語法樹,body 屬性是一個數(shù)組,包含了多個 Statement 節(jié)點(diǎn)。

          interface?Program?{
          ??type:?"Program";
          ??sourceType:?"script"?|?"module";
          ??body:?Array;
          ??comments?:?Array;
          }

          ExpressionStatement


          表達(dá)式語句節(jié)點(diǎn),expression 屬性指向一個表達(dá)式節(jié)點(diǎn)對象

          interface?ExpressionStatement?{
          ??type:?"ExpressionStatement";
          ??expression:?Expression;
          }

          BinaryExpression


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

          本節(jié)實(shí)現(xiàn)的重點(diǎn),簡單理解,我們只要拿到 operator 操作符的類型并實(shí)現(xiàn),然后對 left,right 值進(jìn)行求值即可。

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

          Literal


          字面量,這里不是指 [] 或者 {} 這些,而是本身語義就代表了一個值的字面量,如 1,“hello”, true 這些,還有正則表達(dá)式,如 /\d?/。

          type?Literal?=?SimpleLiteral?|?RegExpLiteral;

          interface?SimpleLiteral?{
          ??type:?"Literal";
          ??value:?string?|?boolean?|?number?|?null;
          ??raw?:?string;
          }

          interface?RegExpLiteral?{
          ??type:?"Literal";
          ??value?:?RegExp?|?null;
          ??regex:?{
          ????pattern:?string;
          ????flags:?string;
          ??};
          ??raw?:?string;
          }

          廢話少說,開擼!!!

          //?standard/es5.ts?實(shí)現(xiàn)以上節(jié)點(diǎn)方法

          import?Scope?from?"../scope";
          import?*?as?ESTree?from?"estree";
          import?{?AstPath?}?from?"../types/index";

          const?es5?=?{
          ??//?根節(jié)點(diǎn)的處理很簡單,我們只要對它的body屬性進(jìn)行遍歷,然后訪問該節(jié)點(diǎn)即可。
          ??Program(node:?ESTree.Program)?{
          ????node.body.forEach((bodyNode)?=>?this.visitNode(bodyNode));
          ??},
          ??//?表達(dá)式語句節(jié)點(diǎn)的處理,同樣訪問expression 屬性即可。
          ??ExpressionStatement(node:?ESTree.ExpressionStatement>)?{
          ????return?this.visitNode(node.expression);
          ??},
          ??//?字面量節(jié)點(diǎn)處理直接求值,這里對正則表達(dá)式類型進(jìn)行了特殊處理,其他類型直接返回value值即可。
          ??Literal(node:?ESTree.Literal>)?{
          ????if?((node).regex)?{
          ??????const?{?pattern,?flags?}?=?(node).regex;
          ??????return?new?RegExp(pattern,?flags);
          ????}?else?return?node.value;
          ??},
          ??//?二元運(yùn)算表達(dá)式節(jié)點(diǎn)處理
          ??//?對left/node兩個節(jié)點(diǎn)(Literal)進(jìn)行求值,然后實(shí)現(xiàn)operator類型運(yùn)算,返回結(jié)果。
          ??BinaryExpression(node:?ESTree.BinaryExpression>)?{
          ????const?leftNode?=?this.visitNode(node.left);
          ????const?operator?=?node.operator;
          ????const?rightNode?=?this.visitNode(node.right);
          ????return?{
          ??????"+":?(l,?r)?=>?l?+?r,
          ??????"-":?(l,?r)?=>?l?-?r,
          ??????"*":?(l,?r)?=>?l?*?r,
          ??????"/":?(l,?r)?=>?l?/?r,
          ??????"%":?(l,?r)?=>?l?%?r,
          ??????"<":?(l,?r)?=>?l???????">":?(l,?r)?=>?l?>?r,
          ??????"<=":?(l,?r)?=>?l?<=?r,
          ??????">=":?(l,?r)?=>?l?>=?r,
          ??????"==":?(l,?r)?=>?l?==?r,
          ??????"===":?(l,?r)?=>?l?===?r,
          ??????"!=":?(l,?r)?=>?l?!=?r,
          ??????"!==":?(l,?r)?=>?l?!==?r,
          ????}[operator](leftNode,?rightNode);
          ??},
          };
          export?default?es5;
          //?visitor.ts
          import?Scope?from?"./scope";
          import?*?as?ESTree?from?"estree";
          import?es5?from?"./standard/es5";

          const?VISITOR?=?{
          ??...es5,
          };
          class?Visitor?{
          ??//?實(shí)現(xiàn)訪問節(jié)點(diǎn)方法,通過節(jié)點(diǎn)類型訪問對應(yīng)的節(jié)點(diǎn)方法
          ??visitNode(node:?ESTree.Node)?{
          ????return?{
          ??????visitNode:?this.visitNode,
          ??????...VISITOR,
          ????}[node.type](node);
          ??}
          }
          export?default?Visitor;

          就這樣,普通的二元運(yùn)算就搞定啦!!!

          實(shí)踐第 2 彈: 怎么找到變量?


          Javascript 的作用域與作用域鏈的概念想必大家都很熟悉了,這里就不再啰嗦了~

          是的,我們需要通過實(shí)現(xiàn)作用域來訪問變量,實(shí)現(xiàn)作用域鏈來搜尋標(biāo)識符。

          在這之前,我們先實(shí)現(xiàn) Variable 類,實(shí)現(xiàn)變量的存取方法。

          //?variable.ts
          export?enum?Kind?{
          ??var?=?"var",
          ??let?=?"let",
          ??const?=?"const",
          }
          export?type?KindType?=?"var"?|?"let"?|?"const";
          export?class?Variable?{
          ??private?_value:?any;
          ??constructor(public?kind:?Kind,?val:?any)?{
          ????this._value?=?val;
          ??}
          ??get?value()?{
          ????return?this._value;
          ??}
          ??set?value(val:?any)?{
          ????this._value?=?val;
          ??}
          }
          import?{?Variable,?Kind,?KindType?}?from?"./variable";

          class?Scope?{
          ??//?父作用域
          ??private?parent:?Scope?|?null;
          ??//?當(dāng)前作用域
          ??private?targetScope:?{?[key:?string]:?any?};
          ??constructor(public?readonly?type,?parent?:?Scope)?{
          ????this.parent?=?parent?||?null;
          ????this.targetScope?=?new?Map();
          ??}
          ??//?是否已定義
          ??private?hasDefinition(rawName:?string):?boolean?{
          ????return?Boolean(this.search(rawName));
          ??}
          ??//?var類型變量定義
          ??public?defineVar(rawName:?string,?value:?any)?{
          ????let?scope:?Scope?=?this;
          ????//?如果不是全局作用域且不是函數(shù)作用域,找到全局作用域,存儲變量
          ????//?這里就是我們常說的Hoisting?(變量提升)
          ????while?(scope.parent?&&?scope.type?!==?"function")?{
          ??????scope?=?scope.parent;
          ????}
          ????//?存儲變量
          ????scope.targetScope.set(rawName,?new?Variable(Kind.var,?value));
          ??}
          ??//?let類型變量定義
          ??public?defineLet(rawName:?string,?value:?any)?{
          ????this.targetScope.set(rawName,?new?Variable(Kind.let,?value));
          ??}
          ??//?const類型變量定義
          ??public?defineConst(rawName:?string,?value:?any)?{
          ????this.targetScope.set(rawName,?new?Variable(Kind.const,?value));
          ??}
          ??//?作用域鏈實(shí)現(xiàn),向上查找標(biāo)識符
          ??public?search(rawName:?string):?Variable?|?null?{
          ????if?(this.targetScope.get(rawName))?{
          ??????return?this.targetScope.get(rawName);
          ????}?else?if?(this.parent)?{
          ??????return?this.parent.search(rawName);
          ????}?else?{
          ??????return?null;
          ????}
          ??}
          ??//?變量聲明方法,變量已定義則拋出語法錯誤異常
          ??public?declare(kind:?Kind?|?KindType,?rawName:?string,?value:?any)?{
          ????if?(this.hasDefinition(rawName))?{
          ??????console.error(
          ????????`Uncaught?SyntaxError:?Identifier?'${rawName}'?has?already?been?declared`
          ??????);
          ??????return?true;
          ????}
          ????return?{
          ??????[Kind.var]:?()?=>?this.defineVar(rawName,?value),
          ??????[Kind.let]:?()?=>?this.defineLet(rawName,?value),
          ??????[Kind.const]:?()?=>?this.defineConst(rawName,?value),
          ????}[kind]();
          ??}
          }

          export?default?Scope;

          以上就是變量對象,作用域及作用域鏈的基礎(chǔ)實(shí)現(xiàn)了,接下來我們就可以定義及訪問變量了。

          實(shí)踐第 3 彈: var age = 18



          從語法樹中我們可以看到三個陌生的節(jié)點(diǎn)類型,來看看它們分別代表什么意思:

          VariableDeclaration


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

          interface?VariableDeclaration?{
          ??type:?"VariableDeclaration";
          ??declarations:?Array;
          ??kind:?"var"?|?"let"?|?"const";
          }

          VariableDeclarator


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

          interface?VariableDeclarator?{
          ??type:?"VariableDeclarator";
          ??id:?Pattern;
          ??init?:?Expression?|?null;
          }

          Identifier


          顧名思義,標(biāo)識符節(jié)點(diǎn),我們寫 JS 時定義的變量名,函數(shù)名,屬性名,都?xì)w為標(biāo)識符。

          interface?Identifier?{
          ??type:?"Identifier";
          ??name:?string;
          }

          了解了對應(yīng)節(jié)點(diǎn)的含義后,我們來進(jìn)行實(shí)現(xiàn):

          //?standard/es5.ts?實(shí)現(xiàn)以上節(jié)點(diǎn)方法

          import?Scope?from?"../scope";
          import?*?as?ESTree?from?"estree";

          type?AstPath?=?{
          ??node:?T;
          ??scope:?Scope;
          };

          const?es5?=?{
          ??//?...
          ??//?這里我們定義了astPath,新增了scope作用域參數(shù)
          ??VariableDeclaration(astPath:?AstPath)?{
          ????const?{?node,?scope?}?=?astPath;
          ????const?{?declarations,?kind?}?=?node;
          ????//?上面提到,生聲明可能存在多個描述(let?a?=?1,?b?=?2;),所以我們這里對它進(jìn)行遍歷:
          ????//?這里遍歷出來的每個item是VariableDeclarator節(jié)點(diǎn)
          ????declarations.forEach((declar)?=>?{
          ??????const?{?id,?init?}?=?declar;
          ??????//?變量名稱節(jié)點(diǎn),這里拿到的是age
          ??????const?key?=?(id).name;
          ??????//?判斷變量是否進(jìn)行了初始化???查找init節(jié)點(diǎn)值(Literal類型直接返回值:18)?:?置為undefined;
          ??????const?value?=?init???this.visitNode(init,?scope)?:?undefined;
          ??????//?根據(jù)不同的kind(var/const/let)聲明進(jìn)行定義,即var?age?=?18
          ??????scope.declare(kind,?key,?value);
          ????});
          ??},
          ??//?標(biāo)識符節(jié)點(diǎn),我們只要通過訪問作用域,訪問該值即可。
          ??Identifier(astPath:?AstPath)?{
          ????const?{?node,?scope?}?=?astPath;
          ????const?name?=?node.name;
          ????//?walk?identifier
          ????//?這個例子中查找的是age變量
          ????const?variable?=?scope.search(name);
          ????//?返回的是定義的變量對象(age)的值,即18
          ????if?(variable)?return?variable.value;
          ??},
          };
          export?default?es5;

          實(shí)踐第 4 彈: module.exports = 6


          我們先來看看 module.exports = 6 對應(yīng)的 AST。


          從語法樹中我們又看到兩個陌生的節(jié)點(diǎn)類型,來看看它們分別代表什么意思:

          AssignmentExpression


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

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

          MemberExpression


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

          interface?MemberExpression?{
          ??type:?"MemberExpression";
          ??object:?Expression?|?Super;
          ??property:?Expression;
          ??computed:?boolean;
          ??optional:?boolean;
          }

          我們先來定義 module.exports 變量。

          import?Scope?from?"./scope";
          import?Visitor?from?"./visitor";
          import?*?as?ESTree?from?"estree";
          class?Interpreter?{
          ??private?scope:?Scope;
          ??private?visitor:?Visitor;
          ??constructor(visitor:?Visitor)?{
          ????this.visitor?=?visitor;
          ??}
          ??interpret(node:?ESTree.Node)?{
          ????this.createScope();
          ????this.visitor.visitNode(node,?this.scope);
          ????return?this.exportResult();
          ??}
          ??createScope()?{
          ????//?創(chuàng)建全局作用域
          ????this.scope?=?new?Scope("root");
          ????//?定義module.exports
          ????const?$exports?=?{};
          ????const?$module?=?{?exports:?$exports?};
          ????this.scope.defineConst("module",?$module);
          ????this.scope.defineVar("exports",?$exports);
          ??}
          ??//?模擬commonjs,對外暴露結(jié)果
          ??exportResult()?{
          ????//?查找module變量
          ????const?moduleExport?=?this.scope.search("module");
          ????//?返回module.exports值
          ????return?moduleExport???moduleExport.value.exports?:?null;
          ??}
          }
          export?default?Interpreter;

          ok,下面我們來實(shí)現(xiàn)以上節(jié)點(diǎn)函數(shù)~

          //?standard/es5.ts?實(shí)現(xiàn)以上節(jié)點(diǎn)方法

          import?Scope?from?"../scope";
          import?*?as?ESTree?from?"estree";

          type?AstPath?=?{
          ??node:?T;
          ??scope:?Scope;
          };

          const?es5?=?{
          ??//?...
          ??//?這里我們定義了astPath,新增了scope作用域參數(shù)
          ??MemberExpression(astPath:?AstPath)?{
          ????const?{?node,?scope?}?=?astPath;
          ????const?{?object,?property,?computed?}?=?node;
          ????//?property?是表示屬性名稱,computed?如果為?false,property?應(yīng)該為一個?Identifier?節(jié)點(diǎn),如果?computed?屬性為?true,即?property?是一個?Expression?節(jié)點(diǎn)
          ????//?這里我們拿到的是exports這個key值,即屬性名稱
          ????const?prop?=?computed
          ????????this.visitNode(property,?scope)
          ??????:?(property).name;
          ????//?object?表示對象,這里為module,對module進(jìn)行節(jié)點(diǎn)訪問
          ????const?obj?=?this.visitNode(object,?scope);
          ????//?訪問module.exports值
          ????return?obj[prop];
          ??},
          ??//?賦值表達(dá)式節(jié)點(diǎn)
          ??(astPath:?AstPath)?{
          ????const?{?node,?scope?}?=?astPath;
          ????const?{?left,?operator,?right?}?=?node;
          ????let?assignVar;
          ????//?LHS?處理
          ????if?(left.type?===?"Identifier")?{
          ??????//?標(biāo)識符類型?直接查找
          ??????const?value?=?scope.search(left.name);
          ??????assignVar?=?value;
          ????}?else?if?(left.type?===?"MemberExpression")?{
          ??????//?成員表達(dá)式類型,處理方式跟上面差不多,不同的是這邊需要自定義一個變量對象的實(shí)現(xiàn)
          ??????const?{?object,?property,?computed?}?=?left;
          ??????const?obj?=?this.visitNode(object,?scope);
          ??????const?key?=?computed
          ??????????this.visitNode(property,?scope)
          ????????:?(property).name;
          ??????assignVar?=?{
          ????????get?value()?{
          ??????????return?obj[key];
          ????????},
          ????????set?value(v)?{
          ??????????obj[key]?=?v;
          ????????},
          ??????};
          ????}
          ????//?RHS
          ????//?不同操作符處理,查詢到right節(jié)點(diǎn)值,對left節(jié)點(diǎn)進(jìn)行賦值。
          ????return?{
          ??????"=":?(v)?=>?{
          ????????assignVar.value?=?v;
          ????????return?v;
          ??????},
          ??????"+=":?(v)?=>?{
          ????????const?value?=?assignVar.value;
          ????????assignVar.value?=?v?+?value;
          ????????return?assignVar.value;
          ??????},
          ??????"-=":?(v)?=>?{
          ????????const?value?=?assignVar.value;
          ????????assignVar.value?=?value?-?v;
          ????????return?assignVar.value;
          ??????},
          ??????"*=":?(v)?=>?{
          ????????const?value?=?assignVar.value;
          ????????assignVar.value?=?v?*?value;
          ????????return?assignVar.value;
          ??????},
          ??????"/=":?(v)?=>?{
          ????????const?value?=?assignVar.value;
          ????????assignVar.value?=?value?/?v;
          ????????return?assignVar.value;
          ??????},
          ??????"%=":?(v)?=>?{
          ????????const?value?=?assignVar.value;
          ????????assignVar.value?=?value?%?v;
          ????????return?assignVar.value;
          ??????},
          ????}[operator](this.visitNode(right,?scope));
          ??},
          };
          export?default?es5;

          ok,實(shí)現(xiàn)完畢,是時候驗(yàn)證一波了,上 jest 大法。

          //?__test__/es5.test.ts

          import?{?run?}?from?"../src/vm";
          describe("giao-js?es5",?()?=>?{
          ??test("assign",?()?=>?{
          ????expect(
          ??????run(`
          ??????module.exports?=?6;
          ????`)
          ????).toBe(6);
          ??});
          }


          實(shí)踐第 5 彈: for 循環(huán)


          var?result?=?0;
          for?(var?i?=?0;?i???result?+=?2;
          }
          module.exports?=?result;


          到這一彈大家都發(fā)現(xiàn)了,不同的語法其實(shí)對應(yīng)的就是不同的樹節(jié)點(diǎn),我們只要實(shí)現(xiàn)對應(yīng)的節(jié)點(diǎn)函數(shù)即可.我們先來看看這幾個陌生節(jié)點(diǎn)的含義.

          ForStatement


          for 循環(huán)語句節(jié)點(diǎn),屬性 init/test/update 分別表示了 for 語句括號中的三個表達(dá)式,初始化值,循環(huán)判斷條件,每次循環(huán)執(zhí)行的變量更新語句(init 可以是變量聲明或者表達(dá)式)。

          這三個屬性都可以為 null,即 for(;;){}。
          body 屬性用以表示要循環(huán)執(zhí)行的語句。

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

          UpdateExpression


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

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

          BlockStatement


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

          interface?BlockStatement?{
          ??0;
          ??type:?"BlockStatement";
          ??body:?Array;
          ??innerComments?:?Array;
          }

          廢話少說,盤它!!!

          //?standard/es5.ts?實(shí)現(xiàn)以上節(jié)點(diǎn)方法

          import?Scope?from?"../scope";
          import?*?as?ESTree?from?"estree";

          type?AstPath?=?{
          ??node:?T;
          ??scope:?Scope;
          };

          const?es5?=?{
          ??//?...
          ??//?for?循環(huán)語句節(jié)點(diǎn)
          ??ForStatement(astPath:?AstPath)?{
          ????const?{?node,?scope?}?=?astPath;
          ????const?{?init,?test,?update,?body?}?=?node;
          ????//?這里需要注意的是需要模擬創(chuàng)建一個塊級作用域
          ????//?前面Scope類實(shí)現(xiàn),var聲明在塊作用域中會被提升,const/let不會
          ????const?forScope?=?new?Scope("block",?scope);
          ????for?(
          ??????//?初始化值
          ??????//?VariableDeclaration
          ??????init???this.visitNode(init,?forScope)?:?null;
          ??????//?循環(huán)判斷條件(BinaryExpression)
          ??????//?二元運(yùn)算表達(dá)式,之前已實(shí)現(xiàn),這里不再細(xì)說
          ??????test???this.visitNode(test,?forScope)?:?true;
          ??????//?變量更新語句(UpdateExpression)
          ??????update???this.visitNode(update,?forScope)?:?null
          ????)?{
          ??????//?BlockStatement
          ??????this.visitNode(body,?forScope);
          ????}
          ??},
          ??//?update?運(yùn)算表達(dá)式節(jié)點(diǎn)
          ??// update 運(yùn)算表達(dá)式節(jié)點(diǎn),即?++/--,和一元運(yùn)算符類似,只是 operator 指向的節(jié)點(diǎn)對象類型不同,這里是 update 運(yùn)算符。
          ??UpdateExpression(astPath:?AstPath)?{
          ????const?{?node,?scope?}?=?astPath;
          ????// update 運(yùn)算符,值為?++?或?--,配合 update 表達(dá)式節(jié)點(diǎn)的 prefix 屬性來表示前后。
          ????const?{?prefix,?argument,?operator?}?=?node;
          ????let?updateVar;
          ????//?這里需要考慮參數(shù)類型還有一種情況是成員表達(dá)式節(jié)點(diǎn)
          ????//?例:?for?(var?query={count:0};?query.count?????//?LHS查找
          ????if?(argument.type?===?"Identifier")?{
          ??????//?標(biāo)識符類型?直接查找
          ??????const?value?=?scope.search(argument.name);
          ??????updateVar?=?value;
          ????}?else?if?(argument.type?===?"MemberExpression")?{
          ??????//?成員表達(dá)式的實(shí)現(xiàn)在前面實(shí)現(xiàn)過,這里不再細(xì)說,一樣的套路~
          ??????const?{?object,?property,?computed?}?=?argument;
          ??????const?obj?=?this.visitNode(object,?scope);
          ??????const?key?=?computed
          ??????????this.visitNode(property,?scope)
          ????????:?(property).name;
          ??????updateVar?=?{
          ????????get?value()?{
          ??????????return?obj[key];
          ????????},
          ????????set?value(v)?{
          ??????????obj[key]?=?v;
          ????????},
          ??????};
          ????}
          ????return?{
          ??????"++":?(v)?=>?{
          ????????const?result?=?v.value;
          ????????v.value?=?result?+?1;
          ????????//?preifx??++i:?i++;
          ????????return?prefix???v.value?:?result;
          ??????},
          ??????"--":?(v)?=>?{
          ????????const?result?=?v.value;
          ????????v.value?=?result?-?1;
          ????????//?preifx??--i:?i--;
          ????????return?prefix???v.value?:?result;
          ??????},
          ????}[operator](updateVar);
          ??},
          ??//?塊語句節(jié)點(diǎn)
          ??//?塊語句的實(shí)現(xiàn)很簡單,模擬創(chuàng)建一個塊作用域,然后遍歷body屬性進(jìn)行訪問即可。
          ??BlockStatement(astPath:?AstPath)?{
          ????const?{?node,?scope?}?=?astPath;
          ????const?blockScope?=?new?Scope("block",?scope);
          ????const?{?body?}?=?node;
          ????body.forEach((bodyNode)?=>?{
          ??????this.visitNode(bodyNode,?blockScope);
          ????});
          ??},
          };
          export?default?es5;

          上 jest 大法驗(yàn)證一哈~

          test("test?for?loop",?()?=>?{
          ??expect(
          ????run(`
          ??????var?result?=?0;
          ??????for?(var?i?=?0;?i?????????result?+=?2;
          ??????}
          ??????module.exports?=?result;
          ????`)
          ??).toBe(10);
          });


          你以為這樣就結(jié)束了嗎? 有沒有想到還有什么情況沒處理? for 循環(huán)的中斷語句呢?

          var?result?=?0;
          for?(var?i?=?0;?i???result?+=?2;
          ??break;?//?break,continue,return
          }
          module.exports?=?result;

          感興趣的小伙伴可以自己動手試試,或者戳源碼地址



          結(jié)語


          giao-js目前只實(shí)現(xiàn)了幾個語法,本文只是提供一個思路。
          有興趣的同學(xué)可以查看完整代碼。



          點(diǎn)擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開更多互動和交流。

          -?END -

          瀏覽 39
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  黑人大鷄巴A片视频 | 操婷婷视频在线观看网站 | jlzz免费 | 18禁男女 | 色婷婷国产AV |