生成器函数是一种具有暂停运行能力的执行模式,在js函数的基础上,生成器函数可以在函数执行过程中暂停自己,但是生成器并不只能暂停自己函数的执行,生成器每次暂停都可以从外界得到一个值,同时在暂停时向外界抛出一个值,和函数外可以建立起一种双向消息传递系统
迭代器和iterable
假如我们需要产生一系列值,每个值都和前面一个有特定的关系,看下面一个例子
const getSeriseValue = () => {
let nextVal;
return () => {
if (nextVal === undefined) {
nextVal = 1;
return nextVal;
}
nextVal = nextVal * 3 + 6;
return nextVal;
}
};
const next = getSetiseValue();
next();//1
next();//9
next();//33
从本质上,可以说我们需要从一个地方持续不断的得到值,直到结束,如果使用迭代器的形式,代码会变成如下
const getSeriesValue = () => {
let nextVal;
return {
[Symbol.iterator]: function() { return this},
next: () => {
if (nextVal === undefined) {
nextVal = 1;
return {value: nextVal, done: false };
}
nextVal = nextVal * 3 + 4;
return {value:nextVal, done:false };
}
}
};
当调用getSeriesValue函数时得到一个对象,这个对象包含一个next函数,调用next函数得到一个对象,这个对象有两个属性, done 是一个boolean值,表示迭代器的状态,为true表示迭代结束,迭代器不会产生值了,value携带产生的值,
js遵守了迭代器的规则,并且在es6的时候推出了for...of循环,这个for...of循环可以不断的执行, 直到得到的结果对象的done是true,for...of循环会调用你的对象内部的Symbol.iterator属性方法,要求返回一个对象,这个对象必须要有next函数,这个next函数必须返回的对象的形式为{value:...,done:....}, (Symbol.iterable是Es6内建的一个标识符, 这个标识符是独一无二的)
const seriesValue = getSeriesValue();
for (let value of seriesValue) {
if (value > 500) {
break;
}
console.log(value);
//1
//7
//25
//79
//241
}
可能最常见的用法是从数组得到一系列值,比如下面
const a = [1, 3, 5, 7, 9];
for (const v of a) {
console.log(v);
}
//1 3 5 7 9
上面能执行成功完全是因为es6让数组对象都拥有了Symbol.iterable属性,调用这个属性函数会得到一个迭代器,自己写的字面量对象,如果你不特意去实现Symbol.iterable方法并且返回一个迭代器对象,是使用不了for...of的
iterable
上面的例子里,迭代器对象都包含一个next, 而当对象有Symbol.iterable属性,并且调用这个函数返回了一个迭代器,就称它为可迭代对象(iterable),上例中的seriesValue就是一个迭代器,同时它有Symbol.iterable属性,这个属性函数调用尽管是返回this,但是因为本身自己就是一个迭代器,也符合调用迭代器的Symbol.iterable得到一个迭代器的规则,所以它同时还是一个可迭代对象,Symbol.iterable函数返回this变成可迭代对象的用法还挺常见的, ,其实for..of就是期待一个可迭代对象,而不是迭代器.
总结
通过上面例子可以得到迭代器和可迭代对象的特性,我们描述这个特性,迭代器要求有一个next函数,而且这个函数返回{value:....,done:....},可迭代对象要求可以通过Symbol.iterable属性函数调用得到一个迭代器,我们把这两种特性描述为迭代器协议和生成器协议
生成器的双向消息传递
function *foo (x) {
const y = x * (yield "Hello");
return y;
}
//执行生成器函数,得到一个迭代器
const it = foo(6);//
//执行迭代对象
let ans = it.next(2);//传入2,没有用,会被忽略
//ans为迭代结果,值为对象{value: "hello world", done: false}
console.log(ans.value); // "Hello"
ans = it.next(7);//替代(yield "Hello") ,变成 x * 7;
console.log(ans.value);//42
可以使用生成器函数生成一个迭代器(注意这里并且没有实现Symbol.Iterable接口,所以它不是可迭代对象,单纯是一个迭代器), 执行next函数,生成器函数就开始运行, 生成器函数内执行到 yield value暂停生成器函数的执行,并且将value抛给外部, 表现为迭代器执行next函数的返回值,这个返回值为一个迭代结果对象,结果如{value: ".....", done: ....}, 当继续next调用的时候传入一个参数,这个值会替代原来yield value 所在的位置继续执行, 如此循环往复,可以看到,
我们第一次调用next函数的时候传入参数是没有用的,因为第一次是用于整个生成器函数开始执行,并不是由yield 暂停,同时最后一次调用next函数的时候,也没有yield抛出一个值了,这个时候return 的值就作为最后一次调用得到的迭代结果对象的value属性的值
生成器迭代器
可迭代对象可以产出迭代器,这个迭代器实现了可迭代协议,同时也是一个可迭代对象,我们还可以覆写上面的例子
function *getSeriesValue() {
let nextVal;
while(true){
if (nextVal === undefined) {
nextVal = 1;
} else {
nextVal = nextVal * 3 + 6;
}
yield nextVal;
}
}
for (let v of getSeriesValue()) {
if (v > 500) {
break;
}
console.log(v);
}
for...of调用break后,会向迭代器发送一个信号终止,也可以使用迭代器的return函数,调用return函数后,所有的所需yield都会被忽略,传递给return函数的参数会作为迭代结果对象的value属性的值,
function *getSeriesValue() {
let nextVal;
while (true) {
if (nextVal === undefined) {
nextVal = 1;
} else {
nextVal = nextVal * 3 + 6;
}
yield nextVal;
}
}
const it = getSeriesValue()
for (let v of it) {
if (v > 500) {
console.log(it.return("down").value); //down
}
console.log(v);
}
//1
//9
//33
//105
//321
//down
//969
使用return终止当前的迭代器后,done为true,所以for...of也就不会继续循环下去了。
总结
基于上述,我们可以认为生成器的一个用法就是用于生产一系列值,它生成一个迭代器同时也是可迭代对象,可以给for...of连续产出一系列值,或者自己next调用非连续的产出值