ES6 系列之 Babel 將 Generator 編譯成了什麼樣子
前言
本文就是簡單介紹下 Generator 語法編譯後的程式碼。
Generator
function* 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 官網的 貼上上述程式碼,然後檢視程式碼被編譯成了什麼樣子:
/** * 我們就稱呼這個版本為簡單編譯版本吧 */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
如果你想看到完整可用的程式碼,你可以使用 ,這是 facebook 下的一個工具,用於編譯 ES6 的 generator 函式。
我們先安裝一下 regenerator:
npm install -g regenerator
然後新建一個 generator.js 檔案,裡面的程式碼就是文章最一開始的程式碼,我們執行命令:
regenerator --include-runtime generator.js > generator-es5.js
我們就可以在 generator-es5.js 檔案看到編譯後的完整可用的程式碼。
而這一編譯就編譯了 700 多行…… 編譯後的程式碼可以檢視
總之編譯後的程式碼還蠻複雜,我們可以從中抽離出大致的邏輯,至少讓簡單編譯的那段程式碼能夠跑起來。
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";
這段程式碼構建了一堆看起來很複雜的關係鏈,其實這是參照著 構建的關係鏈:
圖中
+@@toStringTag:s = 'Generator'
的就是 Gp,
+@@toStringTag:s = 'GeneratorFunction'
的就是 GeneratorFunctionPrototype。
構建關係鏈的目的在於判斷關係的時候能夠跟原生的保持一致,就比如:
function* f() {}var g = f();console.log(g.__proto__ === f.prototype); // trueconsole.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());
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31557424/viewspace-2219131/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- ES6系列之Babel將Generator編譯成了什麼樣子Babel編譯
- ES6 系列之 Babel 將 Async 編譯成了什麼樣子Babel編譯
- ES6 系列之 Babel 是如何編譯 Class 的(上)Babel編譯
- ES6 系列之 Babel 是如何編譯 Class 的(下)Babel編譯
- ES6系列之Babel是如何編譯Class的(上)Babel編譯
- babel是如何編譯es6 class和extends的Babel編譯
- ES6 系列之 Generator 的自動執行
- 探索es6系列之—-Generator生成器函式函式
- ES6之Iterator、Generator
- [譯] Javascript(ES6)Generator 入門JavaScript
- JavaScript編譯器BabelJavaScript編譯Babel
- es6之generator詳解
- js es6深入應用系列(Generator)JS
- Nodejs與ES6系列3:generator物件NodeJS物件
- webpack4 系列教程(二): 編譯 ES6Web編譯
- 【譯】什麼是JavaScript generator 以及如何使用它們JavaScript
- [譯] Javascript 中最長的關鍵字序列長什麼樣子?JavaScript
- Kafka科普系列 | Kafka中的事務是什麼樣子的?Kafka
- Android編譯時註解框架系列1-什麼是編譯時註解Android編譯框架
- 將來的惡意軟體會是什麼樣子?(轉)
- ES6和BabelBabel
- ? babel到底是什麼❓Babel
- 使用Babel轉碼 將es6轉為es5Babel
- ES6 Generator函式函式
- ES6 - symbol&generatorSymbol
- 深入解析 ES6:Generator
- React Native babel編譯異常問題React NativeBabel編譯
- Java條件編譯是什麼?Java編譯
- 看清楚 將來的惡意軟體會是什麼樣子(轉)
- ES6 -- Babel 轉碼器Babel
- ES6 Generator 基礎指南
- 手寫 Vue2 系列 之 編譯器Vue編譯
- 從AST編譯解析談到寫babel外掛AST編譯Babel
- es6 快速入門 系列 —— 迭代器 (Iterator) 和 生成器 (Generator)
- [譯]20 年後比特幣將會變成什麼樣-第 3 部分比特幣
- Promise、Generator、Async有什麼區別?Promise
- 使用 Babel 將基於 ES6 的 SAP UI5 的程式碼轉譯成傳統 JavaScript 程式碼BabelUIJavaScript
- ES6 系列之模板字串字串