生成器是ES6新增的一个极为灵活的结构,拥有在一个函数块内暂停和恢复代码执行的能力。【使用生成器可以自定义迭代器和实现协程】
<1> 生成器
生成器本质上就是一个函数,只要在函数名称前面加一个星号(*) ,就表示他是一个生成器。即只要是可以定义函数的地方,就可以定义生成器。
//生成器函数声明
function* generatorFn(){}
//生成器函数表达式
let generatorFn=function*(){}
//作为对象字面量方法的生成器函数
let foo={
*generatorFn(){}
}
//作为类实例方法的生成器函数
class Foo{
*generatorFn(){}
}
//作为类静态方法的生成器函数
class Bar{
static *generatorFn(){}
}
//等价的生成器函数
function* generatorFnA(){}
function *generatorFnB(){}
function * generatorFnc(){}
//等价的生成器方法
class Foo{
*generatorFnD(){}
* generatorFnE(){}
}
<2> 生成器对象
生成器对象 : 调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行(suspended)的状态。而调用生成器对象上的
next()方法,会让生成器开始或恢复执行。
注意:
- 可以用一个生成器函数生成多个生成器对象。且在一个生成器对象上调用next()不会影响其他生成器对象。
(1) next()方法
- next()方法的返回值类似于迭代器,有一个done属性和一个value属性。
-
只有在生成器对象上初次调用
next()方法,生成器函数才会开始执行!!! -
生成器对象实现了Iterable接口,它们默认的迭代器是自引用的【即在调用生成器函数时,会自动调用生成器函数所返回的生成器对象上的迭代器属性】
(2)生成器对象是可迭代对象
function* generatorFn(){
yield 1;
yield 2;
yield 3;
}
for (const x of generatorFn()){
console.log(x)
}
//1
//2
//3
在需要自定义迭代对象时,这样使用生成器对象会特别有用!!!
function* nTimes(n){
while(n--){
yield;
}
}
for(let _ of nTimes(3)){
console.log('foo') ;
}
//foo
//foo
//foo
<3> yield 中断执行
(1) yield 用法
yield关键字可以让生成器停止和开始执行,也是生成器最有用的地方。【即生成器函数在遇到yield关键字之前会正常执行。遇到这个关键字之后,执行会停止,函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用next()方法来恢复执行】
- 此时的 yield 关键字点像函数的中间返回语句,它生成的值会出现在next()方法返回的对象里。
注意:
- 过yield关键字退出的生成器函数会处在done:false状态;
- 通过return关键字退出的生成器函数会处于done:true状态。
(2) 利用 yield 在函数中实现输入输出
-
输出: yiled 后面的值 x ,会出现在调用 next( ) 方法后,返回的对象中 { done : false , value : x }
-
输入: 第一次调用 next() 方法除外,剩下的每次调用 next() 方法,往方法里面传的参数,会被对应的 yiled 接收,此时 yield 可看作是变量!!!
function* generatorFn(initial){
console.log(initial);
console.log(yield);
console.log(yield);
}
let generatorObject=generatorFn('foo');
generatorObject.next('bar'); //foo
generatorObject.next('baz'); //baz
generatorObject.next('qux'); //qux
(3) 利用 *yield 迭代可迭代对象,使其每次产出一个值
【yield*实际上只是将一个可迭代对象序列化为一串可以单独产出的值】
function* generatorFn(){
yield* [1,2,3];
}
/等价的generatorFn:
// function* generatorFn(){
// for(const x of [1,2,3]){
// yield x;
// }
//}
let generatorObject=generatorFn();
for (const x of generatorFn()){
console.log(x);
}
//1
//2
//3
(4) yiled* 的返回值
- yield* 的值是关联迭代器返回done:true时的value属性。对于普通迭代器来说,这个值是undefined
function* generatorFn(){
console.log('iter value:',yield*[1,2,3]);
}
for(const x of generatorFn()){
console.log('value:',x);
}
//value:1
//value:2
//value:3
//iter value:undefined
- 对于生成器函数产生的迭代器(生成器对象)来说,这个值就是生成器函数返回的值
function* innerGeneratorFn(){
yield'foo';
return 'bar';
}
function* outerGeneratorFn(genObj){
console.log('iter value:',yield* innerGeneratorFn());
}
for (const x of outerGeneratorFn()){
console.log('value:',x);
}
//value:foo
//iter value:bar
- 利用该特性 ,可以实现递归算法,此时生成器可以产生自身
function* nTimes(n){
if(n>0){
yield* nTimes(n-1);
yield n-1;
}
}
for (const x of nTimes(3)){
console.log(x);
}
//0
//1
//2
<4> 提前终止生成器
-
return( )
- 传给 return() 方法的值 x,会出现在调用 return() 方法返回的对象中 { done : ture , value : x} -
throw( )
- throw() 方法会把一个错误注入到生成器对象中,如果错误被捕获处理了,生成器就不会发生中断,会继续进行,如果未被处理,生成器函数就会中断进行!!!
- 并且如果处理了,生成器会跳过 此时的yiled ,即会跳过一个值!!!(p204)
function* generatorFn(){
for(const x of [1,2,3]){
try{
yield x;
}catch(e){}
}
}
const g=generatorFn();
console.log(g.next()); //{done:false,value:1}
g.throw('foo');
console.log(g.next()); //{done:false,value:3}
<5> 生成器的自动执行
Generator 函数的自动执行需要一种机制,即当异步操作有了结果,能够自动交回执行权。
而两种方法可以做到这一点。
(1)回调函数。将异步操作进行包装,暴露出回调函数,在回调函数里面交回执行权。
(2)Promise 对象。将异步操作包装成 Promise 对象,用 then 方法交回执行权。
// 第一版
function run(gen) {
var gen = gen();
function next(data) {
var result = gen.next(data);
if (result.done) return;
if (isPromise(result.value)) {
result.value.then(function(data) {
next(data);
});
} else {
result.value(next)
}
}
next()
}
function isPromise(obj) {
return 'function' == typeof obj.then;
}
module.exports = run;
co是大神 TJ Holowaychuk 于 2013 年 6 月发布的一个小模块,用于 Generator 函数的自动执行。