JS中的Generator有两种类型,一是Generator Function,二是Generator Object。
Generator Function
语法
中文翻译为“生成器函数”,特点是在执行时能暂停,后面又能从暂停处继续执行。
demo
// 1. 创建生成器函数
function* gen() {
const now = Date.now();
// 2. yield左侧收到的数据
const receiveTime = yield now;
return receiveTime - now;
}
// 原型
console.log(gen.__proto__);
// 3. 函数生成的对象
const generator = gen();
// 4. 调用对象的next方法
const result1 = generator.next();
console.log('result1:', result1);
setTimeout(() => {
console.log('\n generator 第二次next调用');
// 5. 第二次调用next,并传参
const result2 = generator.next(Date.now());
console.log('result2:', result2);
}, 300);
/*
GeneratorFunction {}
result1: { value: 1659254017742, done: false }
generator 第二次next调用
result2: { value: 302, done: true }
*/
函数执行说明
- 调用生成器函数,并不会马上执行它内部的语句,而是返回一个Generator对象(后文会提到)
- 第一次调用next方法,函数中的语句会执行到第一个的一个yield位置为止,yield后紧跟iterator要返回的值
- 下一次调用next方法,从暂停的yield处继续向下执行,直到遇到下一个yield表达式
- 如果没有遇到yield表达式,则一直运行函数,直到遇到return,return语句的值则会作为返回值。如果没有return语句,则返回值为undefined
next
-
next方法返回一个对象
{ value: any, // 上面提到的返回值 done: boolean, // 生成器函数是否已经执行完毕并返回 } -
next方法,如果传入了参数,那个这个参数会传给上一条执行的yield语句左边的变量,比如上面demo的第5处
yield
- yield语句后直接跟值,则此值会作为next返回对象的value属性值,比如demo第
- yield* 将控制权交给另外一个生成器函数
function* userGen() { const user = {name: '橘子', age: '18'}; yield user.name; yield user.age; } function* logUser() { const id = 888; yield id; yield* userGen(); // yield* yield 'end'; } const iterator = logUser(); console.log('1 ' ,iterator.next()); console.log('2 ' ,iterator.next()); console.log('3 ' ,iterator.next()); console.log('4 ' ,iterator.next()); console.log('5 ' ,iterator.next()); /* 运行结果 1 { value: 888, done: false } 2 { value: '橘子', done: false } 3 { value: '18', done: false } 4 { value: 'end', done: false } 5 { value: undefined, done: true } */
ES5实现
Generator Function是ES2015的新特性,它在低版本浏览器是如何支持的?如果用ES5来实现此函数?
测试函数
function* userGen() {
const user = {name: '橘子', age: '18'};
yield user.name;
yield user.age;
}
const iterator = userGen();
iterator.next();
babel转换
以数userGen为例,查看babel的转换结果
- _regeneratorRuntime().mark函数的作用是?
2. _regeneratorRuntime().wrap函数的作用是?
3. context对象的作用?
4. 更详细的解析可参考冴羽的ES6 系列之 Babel 将 Generator 编译成了什么样子
简易版本
babel的代码全面且复杂,但可以从中抽出关键点来实现最简易的运行版本
function userGen() {
var user;
// 1. 使用自定义的wrap函数
return wrap(function userGen$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
user = {name: "橘子",age: "18"};
_context.next = 3;
return user.name;
case 3:
_context.next = 5;
return user.age;
case 5:
case "end":
return _context.stop();
}
}
});
}
// 2. 自定义wrap,返回一个带有next方法的对象
function wrap(genFun) {
// genFun函数执行上下文
const context = {
prev: 0,
next: 0,
done: false,
stop: function() {
this.done = true;
}
};
return {
next: function() {
// 返回结构 value, done
// genFun调用会修改context的next和prev值
return { value: genFun(context), done: context.done };
}
}
}
// 得到(类)生成器对象 & next方法调用
const iterator = userGen();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
/*
{ value: '橘子', done: false }
{ value: '18', done: false }
{ value: undefined, done: true }
*/
Generator Object
原型
上文我们提到了Generator Function会返回一个Generator对象,它有next方法。 此对象在JS中的原型为Generator。原型上除了next方法外,还有两个方法。
- Generator.prototype.return 返回给定的值并结束生成器
function* userGen() { const user = {name: '橘子', age: '18'}; yield user.name; yield user.age; } const iterator = userGen(); console.log(iterator.__proto__); console.log(iterator.return('直接结束')); console.log(iterator.next()); /* Object [Generator] {} { value: '直接结束', done: true } { value: undefined, done: true } */ - Generator.prototype.throw 向生成器抛出一个错误
- throw方法被生成器函数内部捕获之后,会执行下一条可执行的yield表达式,和调用一次 next效果相同
- throw 方法也有返回值
function* userGen() { const user = {name: '橘子', age: '18'}; try{ yield user.name; yield console.log('user name: ' + user.name); }catch(e){ console.log('函数内部捕获错误:', e.message); } yield console.log('user age: ' + user.age); yield 'end'; } const iterator = userGen(); iterator.next(); console.log(iterator.throw(new Error('这个是错误'))); /* 执行结果为: 函数内部捕获错误: 这个是错误 user age: 18 { value: undefined, done: false } */- 如果throw方法执行时,next方法一次都没有执行过,会在函数外抛出错误(此时生成器函数还未开始执行,只能在函数外部抛出)
function* userGen() { // 同上 } const iterator = userGen(); try{ console.log(iterator.throw(new Error('这个是错误'))); }catch(e){ console.log('全局捕获错误: ', e.message); console.log(iterator.next()); } /*执行结果为: 全局捕获错误: 这个是错误 { value: undefined, done: true } */