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 }
tryCatch
办法 会捕捉 gen$
中抛出的异样。如果有捕捉到,则会返回异样的状态
// 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(…)括号里的三局部会被拆成三个局部
- initialization – 初始化语句 对应 case 0
- condition – 条件 对应 case 1 中结尾局部
- final expression – 最终表达式 对应 case 6
for循环外部逻辑:
- case 1 和 case 5 别离示意for的大括号外面,yield前和yield后代码。
参考:
ES6 系列之 Babel 将 Generator 编译成了什么样子
从协程到状态机–regenerator源码解析(一)
发表回复