摘要:前言本文就是簡單介紹下語法編譯后的代碼。如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎,對作者也是一種鼓勵。
前言
本文就是簡單介紹下 Generator 語法編譯后的代碼。
Generatorfunction* helloWorldGenerator() { yield "hello"; yield "world"; return "ending"; }
我們打印下執行的結果:
var hw = helloWorldGenerator(); console.log(hw.next()); // {value: "hello", done: false} console.log(hw.next()); // {value: "world", done: false} console.log(hw.next()); // {value: "ending", done: true} console.log(hw.next()); // {value: undefined, done: true}Babel
具體的執行過程就不說了,我們直接在 Babel 官網的 Try it out 粘貼上述代碼,然后查看代碼被編譯成了什么樣子:
/** * 我們就稱呼這個版本為簡單編譯版本吧 */ var _marked = /*#__PURE__*/ regeneratorRuntime.mark(helloWorldGenerator); function helloWorldGenerator() { return regeneratorRuntime.wrap( function helloWorldGenerator$(_context) { while (1) { switch ((_context.prev = _context.next)) { case 0: _context.next = 2; return "hello"; case 2: _context.next = 4; return "world"; case 4: return _context.abrupt("return", "ending"); case 5: case "end": return _context.stop(); } } }, _marked, this ); }
猛一看,好像編譯后的代碼還蠻少的,但是細細一看,編譯后的代碼肯定是不能用的呀,regeneratorRuntime 是個什么鬼?哪里有聲明呀?mark 和 wrap 方法又都做了什么?
難道就不能編譯一個完整可用的代碼嗎?
regenerator如果你想看到完整可用的代碼,你可以使用 regenerator,這是 facebook 下的一個工具,用于編譯 ES6 的 generator 函數。
我們先安裝一下 regenerator:
npm install -g regenerator
然后新建一個 generator.js 文件,里面的代碼就是文章最一開始的代碼,我們執行命令:
regenerator --include-runtime generator.js > generator-es5.js
我們就可以在 generator-es5.js 文件看到編譯后的完整可用的代碼。
而這一編譯就編譯了 700 多行…… 編譯后的代碼可以查看 generator-es5.js
總之編譯后的代碼還蠻復雜,我們可以從中抽離出大致的邏輯,至少讓簡單編譯的那段代碼能夠跑起來。
mark 函數簡單編譯后的代碼第一段是這樣的:
var _marked = /*#__PURE__*/ regeneratorRuntime.mark(helloWorldGenerator);
我們查看完整編譯版本中 mark 函數的源碼:
runtime.mark = function(genFun) { genFun.__proto__ = GeneratorFunctionPrototype; genFun.prototype = Object.create(Gp); return genFun; };
這其中又涉及了 GeneratorFunctionPrototype 和 Gp 變量,我們也查看下對應的代碼:
function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} ... var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype; GeneratorFunctionPrototype.constructor = GeneratorFunction; GeneratorFunctionPrototype[toStringTagSymbol] = GeneratorFunction.displayName = "GeneratorFunction";
這段代碼構建了一堆看起來很復雜的關系鏈,其實這是參照著 ES6 規范構建的關系鏈:
圖中 +@@toStringTag:s = "Generator" 的就是 Gp,+@@toStringTag:s = "GeneratorFunction" 的就是 GeneratorFunctionPrototype。
構建關系鏈的目的在于判斷關系的時候能夠跟原生的保持一致,就比如:
function* f() {} var g = f(); console.log(g.__proto__ === f.prototype); // true console.log(g.__proto__.__proto__ === f.__proto__.prototype); // true
為了簡化起見,我們可以把 Gp 先設置為一個空對象,不過正如你在上圖中看到的,next()、 throw()、return() 函數都是掛載在 Gp 對象上,實際上,在完整的編譯代碼中,確實有為 Gp 添加這三個函數的方法:
// 117 行 function defineIteratorMethods(prototype) { ["next", "throw", "return"].forEach(function(method) { prototype[method] = function(arg) { return this._invoke(method, arg); }; }); } // 406 行 defineIteratorMethods(Gp);
為了簡單起見,我們將整個 mark 函數簡化為:
runtime.mark = function(genFun) { var generator = Object.create({ next: function(arg) { return this._invoke("next", arg) } }); genFun.prototype = generator; return genFun; };wrap 函數
除了設置關系鏈之外,mark 函數的返回值 genFun 還作為了 wrap 函數的第二個參數傳入:
function helloWorldGenerator() { return regeneratorRuntime.wrap( function helloWorldGenerator$(_context) { ... }, _marked, this ); }
我們再看下 wrap 函數:
function wrap(innerFn, outerFn, self) { var generator = Object.create(outerFn.prototype); var context = new Context([]); generator._invoke = makeInvokeMethod(innerFn, self, context); return generator; }
所以當執行 var hw = helloWorldGenerator(); 的時候,其實執行的是 wrap 函數,wrap 函數返回了 generator,generator 是一個對象,原型是 outerFn.prototype, outerFn.prototype 其實就是 genFun.prototype, genFun.prototype 是一個空對象,原型上有 next() 方法。
所以當你執行 hw.next() 的時候,執行的其實是 hw 原型的原型上的 next 函數,next 函數執行的又是 hw 的 _invoke 函數:
generator._invoke = makeInvokeMethod(innerFn, self, context);
innerFn 就是 wrap 包裹的那個函數,其實就是 helloWordGenerato$ 函數,吶,就是這個函數:
function helloWorldGenerator$(_context) { while (1) { switch ((_context.prev = _context.next)) { case 0: _context.next = 2; return "hello"; case 2: _context.next = 4; return "world"; case 4: return _context.abrupt("return", "ending"); case 5: case "end": return _context.stop(); } } }
而 context 你可以直接理解為這樣一個全局對象:
var ContinueSentinel = {}; var context = { done: false, method: "next", next: 0, prev: 0, abrupt: function(type, arg) { var record = {}; record.type = type; record.arg = arg; return this.complete(record); }, complete: function(record, afterLoc) { if (record.type === "return") { this.rval = this.arg = record.arg; this.method = "return"; this.next = "end"; } return ContinueSentinel; }, stop: function() { this.done = true; return this.rval; } };
每次 hw.next 的時候,就會修改 next 和 prev 屬性的值,當在 generator 函數中 return 的時候會執行 abrupt,abrupt 中又會執行 complete,執行完 complete,因為 this.next = end 的緣故,再執行就會執行 stop 函數。
我們來看下 makeInvokeMethod 函數:
var ContinueSentinel = {}; function makeInvokeMethod(innerFn, self, context) { var state = "start"; return function invoke(method, arg) { if (state === "completed") { return { value: undefined, done: true }; } context.method = method; context.arg = arg; while (true) { state = "executing"; var record = { type: "normal", arg: innerFn.call(self, context) }; if (record.type === "normal") { state = context.done ? "completed" : "yield"; if (record.arg === ContinueSentinel) { continue; } return { value: record.arg, done: context.done }; } } }; }
基本的執行過程就不分析了,我們重點看第三次執行 hw.next() 的時候:
第三次執行 hw.next() 的時候,其實執行了
this._invoke("next", undefined);
我們在 invoke 函數中構建了一個 record 對象:
var record = { type: "normal", arg: innerFn.call(self, context) };
而在 innerFn.call(self, context) 中,因為 _context.next 為 4 的緣故,其實執行了:
_context.abrupt("return", "ending");
而在 abrupt 中,我們又構建了一個 record 對象:
var record = {}; record.type = "return"; record.arg = "ending";
然后執行了 this.complete(record),
在 complete 中,因為 record.type === "return"
this.rval = "ending"; this.method = "return"; this.next = "end";
然后返回了全局對象 ContinueSentinel,其實就是一個全局空對象。
然后在 invoke 函數中,因為 record.arg === ContinueSentinel 的緣故,沒有執行后面的 return 語句,就直接進入下一個循環。
于是又執行了一遍 innerFn.call(self, context),此時 _context.next 為 end, 執行了 _context.stop(), 在 stop 函數中:
this.done = true; return this.rval; // this.rval 其實就是 `ending`
所以最終返回的值為:
{ value: "ending", done: true };
之后,我們再執行 hw.next() 的時候,因為 state 已經是 "completed" 的緣故,直接就返回 { value: undefined, done: true}
不完整但可用的源碼當然這個過程,看文字理解起來可能有些難度,不完整但可用的代碼如下,你可以斷點調試查看具體的過程:
(function() { var ContinueSentinel = {}; var mark = function(genFun) { var generator = Object.create({ next: function(arg) { return this._invoke("next", arg); } }); genFun.prototype = generator; return genFun; }; function wrap(innerFn, outerFn, self) { var generator = Object.create(outerFn.prototype); var context = { done: false, method: "next", next: 0, prev: 0, abrupt: function(type, arg) { var record = {}; record.type = type; record.arg = arg; return this.complete(record); }, complete: function(record, afterLoc) { if (record.type === "return") { this.rval = this.arg = record.arg; this.method = "return"; this.next = "end"; } return ContinueSentinel; }, stop: function() { this.done = true; return this.rval; } }; generator._invoke = makeInvokeMethod(innerFn, context); return generator; } function makeInvokeMethod(innerFn, context) { var state = "start"; return function invoke(method, arg) { if (state === "completed") { return { value: undefined, done: true }; } context.method = method; context.arg = arg; while (true) { state = "executing"; var record = { type: "normal", arg: innerFn.call(self, context) }; if (record.type === "normal") { state = context.done ? "completed" : "yield"; if (record.arg === ContinueSentinel) { continue; } return { value: record.arg, done: context.done }; } } }; } window.regeneratorRuntime = {}; regeneratorRuntime.wrap = wrap; regeneratorRuntime.mark = mark; })(); var _marked = regeneratorRuntime.mark(helloWorldGenerator); function helloWorldGenerator() { return regeneratorRuntime.wrap( function helloWorldGenerator$(_context) { while (1) { switch ((_context.prev = _context.next)) { case 0: _context.next = 2; return "hello"; case 2: _context.next = 4; return "world"; case 4: return _context.abrupt("return", "ending"); case 5: case "end": return _context.stop(); } } }, _marked, this ); } var hw = helloWorldGenerator(); console.log(hw.next()); console.log(hw.next()); console.log(hw.next()); console.log(hw.next());ES6 系列
ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog
ES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級作用域、標簽模板、箭頭函數、Symbol、Set、Map 以及 Promise 的模擬實現、模塊加載方案、異步處理等內容。
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98858.html
摘要:大約后輸出我們直接在官網的粘貼上述代碼,然后查看代碼編譯成什么樣子相關的代碼我們在系列之將編譯成了什么樣子中已經介紹過了,這次我們重點來看看函數以上這段代碼主要是用來實現的自動執行以及返回。 前言 本文就是簡單介紹下 Async 語法編譯后的代碼。 Async const fetchData = (data) => new Promise((resolve) => setTimeout...
摘要:字面上是生成器的意思,在里是迭代器生成器,用于生成一個迭代器對象。當執行的時候,并不執行函數體,而是返回一個迭代器。迭代器具有方法,每次調用方法,函數就執行到語句的地方。也有觀點極力反對,認為隱藏了本身原型鏈的語言特性,使其更難理解。 本文為 ES6 系列的第一篇。旨在給新同學一些指引,帶大家走近 ES6 新特性。簡要介紹: 什么是 ES6 它有哪些明星特性 它可以運行在哪些環境 ...
摘要:上集回顧從零開始手把手教你實現一個一上一集我們介紹了什么是,為什么要用,以及我們要怎樣來實現一個。完成后,在命令行中輸入安裝下依賴。最后返回這個目標節點。明天,我們迎接挑戰,開始處理數據變動引起的重新渲染,我們要如何新舊,生成補丁,修改。 上集回顧 從零開始手把手教你實現一個Virtual DOM(一)上一集我們介紹了什么是VDOM,為什么要用VDOM,以及我們要怎樣來實現一個VDOM...
摘要:塊級作用域存在于函數內部塊中字符和之間的區域和塊級聲明用于聲明在指定塊的作用域之外無法訪問的變量。和都是塊級聲明的一種。值得一提的是聲明不允許修改綁定,但允許修改值。這意味著當用聲明對象時沒有問題報錯臨時死區臨時死區,簡寫為。 塊級作用域的出現 通過 var 聲明的變量存在變量提升的特性: if (condition) { var value = 1; } console.lo...
摘要:聲明的變量不得改變值,這意味著,一旦聲明變量,就必須立即初始化,不能留到以后賦值。這在語法上,稱為暫時性死區,簡稱。這表明函數內部的變量與循環變量不在同一個作用域,有各自單獨的作用域。系列文章系列文章地址 showImg(https://segmentfault.com/img/bVbrjjC); 為什么需要塊級作用域 ES5 只有全局作用域和函數作用域,沒有塊級作用域,這帶來很多不合...
閱讀 3433·2021-11-22 09:34
閱讀 1899·2019-08-30 12:53
閱讀 3490·2019-08-28 18:07
閱讀 2977·2019-08-27 10:55
閱讀 2959·2019-08-26 10:12
閱讀 3584·2019-08-23 18:21
閱讀 1338·2019-08-23 14:10
閱讀 1469·2019-08-23 13:04