前言
本文就是簡單介紹下 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 官網的
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
如果你想看到完整可用的代碼,你可以使用
,這是 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/BlogES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的了解,重點講解塊級作用域、标簽模闆、箭頭函數、Symbol、Set、Map 以及 Promise 的模拟實作、子產品加載方案、異步處理等内容。
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。