一、Generator函数简介
- 执行 Generator 函数会返回一个遍历器对象
- function关键字与函数名之间有一个星号function * f () {…}
- 函数体内部使用yield表达式,定义不同的内部状态
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator(); //函数并不执行,而是返回遍历器对象;
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
以上代码有三个状态,hello、world、ending,通过调用hw的next方法,hw便由函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止,Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。
总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
二、next传递参数
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的
无参数的情况下:
第一运行用next方法,系统自动忽略参数,返回x+1的值6;
第二次运行next方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN),因此返回对象的value属性(y/3)也等于NaN;
第三次运行next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5 + NaN + undefined,即NaN。
有参数的情况下:
第一运行用next方法,系统自动忽略参数,返回x+1的值6;
第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8;
第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42。
三、throw方法
Generator 函数返回的遍历器对象,都有一个throw方法(不同于全局throw指令),可以在函数体外抛出错误,然后在 Generator 函数体内捕获。throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法。
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
i.throw(new Error('出错了!'));
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 Error: 出错了!
// 外部捕获 b
catch语句捕获。i第二次抛出错误,由于 Generator 函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。
四、return方法
Generator 函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历 Generator 函数。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true },不带参数则value为undefined
g.next() // { value: undefined, done: true }
如果 Generator 函数内部有try…finally代码块,那么return方法会推迟到finally代码块执行完再执行。
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
五、next、throw、return的共同的点
next()是将yield表达式替换成一个值。
throw()是将yield表达式替换成一个throw语句。
return()是将yield表达式替换成一个return语句。
六、Generator函数实现对象遍历器接口
var myIterable = {
a : 1,
b : 2
};
myIterable[Symbol.iterator] = function* (obj = myIterable) {
let propKeys = Object.keys(obj);
for (let propKey of propKeys) {
yield [propKey, obj[propKey]];
}
};
[...myIterable] // [['a', 1], ['b', 2]]
由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
function* objectEntries(obj) {
let propKeys = Reflect.ownKeys(obj);
for (let propKey of propKeys) {
yield [propKey, obj[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
for (let [key, value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
以上方法,直接将对象转化成iterator遍历器对象,以便直接使用for … of 语句。