生成器函数generator function 用function *name () {}
来声明,调用生成器函数则会返回一个生成器对象generator,并且符合可迭代协议和迭代器协议,因此generator也是一个迭代器对象,具有next()方法,调用next()方法会执行生成器函数内的语句(即遍历生成器函数内部的状态,状态值由yield后的表达式值给出)。
function *foo ( x ) {
yield 1;
var y = x + ( yield 2 );
var z = y + ( yield );
return;
}
var it = foo(3); // x = 3; 首次调用生成器函数返回一个对象,具有next()方法的迭代器对象;
it.next().value; // 1 ;调用next方法来遍历状态,在遇到yield语句时停下来,返回具有{value: value, done: false}形式的对象,其中value值是yield后的表达式值,done表示迭代是否结束;
it.next().value; // 2 第二次调用next方法,从上次yield停下来的地方继续执行,注意上一次只执行yield及其表达式,如果yield表达式包含在语句中,则该语句不会执行,直到再次被启动;
it.next(4).value; // undefined; y = 7; 第三次调用next方法时传入一个值,yield表达式可以接收值,也可以生成值,生成的值会被next方法返回,接受的值由next方法传入作为整个yield表达式的值,所以此时y = 3 + 4; 然后继续执行到下一个yield处(var z = y + (yield) ),由于yield后没有值,所以生成值为undefined;
it.next(5).value; // undefined; z = 12; 第四次调用next方法传入5,可以得到z = 7 + 5; 执行到return结束,返回对象为{value: undefined, done: true};
每次调用生成器函数构建迭代器时,同时隐式构建了生成器实例,迭代器对应执行的是新的生成器实例,因此可以构建多个迭代器来控制对应生成器实例,互相之间并不会干扰。比如
function *bar () {
yield 1;
yield 2;
return;
}
var it1 = bar();
var it2 = bar();
it1.next(); // {value: 1, done: false}
it2.next(); // {value: 1, done: false},并非{value: 2, done: false}
利用生成器实例的暂停执行机制,可以控制多个生成器函数交替执行,如果这些函数有共享的变量,则可以模仿多线程竞态条件环境。
for of遍历方法
典型的for of遍历写法for (let v of something)
,其中something是迭代器对象,或者具有[Symbol.iterator]属性的对象,如数组,Map集合和Set集合,而v则是迭代器对象调用next方法返回对象的value属性值;for of结构中迭代器对象自动调用next方法,直到返回对象的done属性值为true;
如果生成器函数内部有无限多个状态,如
function *foo () {
var nextVal;
while (true) {
if (nextVal === undefined ) {
nextVal = 1;
} else {
nextVal = 2 * nextVal;
}
yield nextVal;
}
}
利用for ( let v of foo())
则会一直无限循环下去,那么如何让生成器停止呢?
给for of添加break、return语句;for of结构在遇到break、return或者有异常时,并非会挂起状态,而是会向迭代器对象发送一个信号使其终止;break和return会触发运行生成器内部的finally语句;
var it = foo();
for ( let v of it ) {
console.log(v);
if ( v > 500 ) {
break; // 或者用return
// it.return('done!'); 迭代器对象调用return方法来发出终止信号,并且设置返回对象为{value: 'done', done: true};
}
}
注意:在可迭代对象(如数组,Map和Set集合)中,迭代对象并没有return方法,只有next方法,这一点跟生成器函数返回的迭代对象有区别;如
var arr = [1, 2, 500, 600,3, 4];
var it = arr[Symbol.iterator]();
for(let v of it) {
console.log(v);
if(v > 500) {
it.return('done!');
}
}
1
2
500
600
TypeError: it.return is not a function