共计 4751 个字符,预计需要花费 12 分钟才能阅读完成。
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 源码解析(一)