regenerator的效果
regenerator是一个es5环境下实现es6的generator的方式,
regenerator 分为编译时和运行时两块,编译工具会负责将generator编译成es5,比如:
这段代码:
const len = 100;
function *gen() {
console.log('gen !');
for (let i = 0; i < len; i++) {
yield i;
}
return 'what';
}
const g = gen();
for (let i = 0; i < len; i++) {
console.log(g.next());
}
会被编译成
'use strict';
var _marked =
/*#__PURE__*/
regeneratorRuntime.mark(gen);
var len = 10;
function gen() {
var i;
return regeneratorRuntime.wrap(function gen$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
console.log('gen !');
i = 0;
case 2:
if (!(i < len)) {
_context.next = 7;
break;
}
_context.next = 5;
return i++;
case 5:
_context.next = 2;
break;
case 7:
return _context.abrupt("return", 'what');
case 8:
case "end":
return _context.stop();
}
}
}, _marked);
}
var g = gen();
for (var i = 0; i < len; i++) {
console.log(g.next());
}
其中的regeneratorRuntime就是regenerator的运行时部分。
编译后产生的 switch
代码可以理解为定义的各个状态,以及状态间转化的逻辑
运行时
运行时我们主要看一下regeneratorRuntime这个对象提供的内容。regeneratorRuntime的源代码地址
regeneratorRumtime在上面编译后的代码里,提供了两个方法,wrap
和 mark
wrap
wrap方法会返回一个generator对象,也就是符合es6标准的迭代器,包含方法:
- next
- throw
- return
按照迭代器的定义,调用next
方法会是迭代器从上次yield的位置后面继续执行。每调用一次next
方法,都会调用一次编译后的gen$
方法。$gen
方法内会修改context上下文中的next值,达到一个驱动迭代器的效果。
gen$
这个函数很像是一个reducer
// line 274
if (context.method === "next") {
// Setting context._sent for legacy support of Babel's
// function.sent implementation.
context.sent = context._sent = context.arg;
} //...
// line 293
var record = tryCatch(innerFn, self, context);
if (record.type === "normal") {
// If an exception is thrown from innerFn, we leave state ===
// GenStateExecuting and loop back for another invocation.
state = context.done
? GenStateCompleted
: GenStateSuspendedYield;
if (record.arg === ContinueSentinel) {
continue;
}
return {
value: record.arg,
done: context.done
};
} // ...
这里的innerFn就是之前的 gen$
函数,tryCatch(innerFn, self, context)
就会调用 gen$
函数,gen$
会返回一个结果,如果没有就会返回 next
方法的标准返回 { value: any, done: boolean }
// line 61
function tryCatch(fn, obj, arg) {
try {
return { type: "normal", arg: fn.call(obj, arg) };
} catch (err) {
return { type: "throw", arg: err };
}
}
mark
mark方法会将gen
迭代器函数的原型指向runtime内部定义的generator对象。
那么regenerator内部是怎么定义generator对象的呢?
// line 82
function Generator() {}
function GeneratorFunction() {}
function GeneratorFunctionPrototype() {}
// This is a polyfill for %IteratorPrototype% for environments that
// don't natively support it.
var IteratorPrototype = {};
IteratorPrototype[iteratorSymbol] = function () {
return this;
};
var getProto = Object.getPrototypeOf;
var NativeIteratorPrototype = getProto && getProto(getProto(values([])));
if (NativeIteratorPrototype &&
NativeIteratorPrototype !== Op &&
hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {
// This environment has a native %IteratorPrototype%; use it instead
// of the polyfill.
IteratorPrototype = NativeIteratorPrototype;
}
var Gp = GeneratorFunctionPrototype.prototype =
Generator.prototype = Object.create(IteratorPrototype);
GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;
GeneratorFunctionPrototype.constructor = GeneratorFunction;
GeneratorFunction.displayName = define(
GeneratorFunctionPrototype,
toStringTagSymbol,
"GeneratorFunction"
);
// line 111
// line 115
function defineIteratorMethods(prototype) {
["next", "throw", "return"].forEach(function(method) {
define(prototype, method, function(arg) {
return this._invoke(method, arg);
});
});
}
// line 406
defineIteratorMethods(Gp);
line 82 - line 111 定义了几个dummy constructor,并且定义了它们之间原型链关系,这些关系的设置是按照 ES6 规范 来设置的。
line 115 defineIteratorMethods 声明了一个定义generator内部方法的函数,函数中 define
方法内部实际是 Object.define
方法的封装。
line 406 使用 defineIteratorMethods,传入Gp,使得Gp成为一个generator对象。defineIteratorMethods定义了next, throw, return 三个方法
context
generator的上下文,即状态机的上下文,提供了多个方法和变量,《runtime.js#L521》
方法:
- reset
- stop
- dispatchException
- abrupt
- complete
- finish
- catch
变量:这些变量的初始化都在reset方法里,reset方法会在构造函数里面被调用
- next number - 状态机的下一个状态值
- pre number - 状态机的上一个状态值
- sent
- done
- delegate
- method
- arg
编译时
编译时,迭代器里的内容会被编译成switch...case形式的代码,其中各个case基本是以yield关键词作为划分case的依据,然后整个switch...case会被包裹在一个while(1)的循环里,只有当某一个case使用return返回时才会跳出while循环。
比如一个简单的,可迭代2次的迭代器
'编译前'
function *gen() {
yield 1;
yield 2;
}
其中的2次yield,在编译结果里,就会被拆成2个case, case 0 和 case 2
'编译后'
function gen() {
return regeneratorRuntime.wrap(function gen$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 1;
case 2:
_context.next = 4;
return 2;
case 4:
case "end":
return _context.stop();
}
}
}, _marked);
}
那如果迭代器里面有for循环呢,是怎么处理的?
'编译前'
function *gen() {
for (let i = 0; i < len; i++) {
before = 'yield前语句'
yield i;
after = 'yield后语句'
}
}
for循环中,每一次循环会被拆成多个case
function gen() {
var i;
return regeneratorRuntime.wrap(function gen$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
i = 0;
case 1:
if (!(i < len)) {
_context.next = 9;
break;
}
before = 'yield前语句';
_context.next = 5;
return i;
case 5:
after = 'yield后语句';
case 6:
i++;
_context.next = 1;
break;
case 9:
case "end":
return _context.stop();
}
}
}, _marked);
}
for 循环会被拆成三大部分,for循环语句,包含三个表达式,即for(...)括号里的三部分,
- initialization - 初始化语句 对应 case 0
- condition - 条件 对应 case 1 中开头部分
- final expression - 最终表达式 对应 case 6
case 1 和 case 5 分别表示for的大括号里面,yield前和yield后代码。