在js中,有一个普遍依赖的假定:一个函数一旦开始执行,就会运行到结束,期间不会有其他代码能够打断它并插入其间。
但ES6中引入了一个新的函数类型,它并不符合这种运行到结束的特性。这类新的函数被称为生成器。
看下面的普通函数示例:
var x = 1;function foo(){
x++;
bar();
console.log(x);
}
function bar(){
x++;
}
foo(); // 3
上例中,bar()会在foo()函数中执行,导致foo()函数执行后x值为3。但如果bar()不在foo函数中,最后的显示结果就是2,而不是3。
那可以想个问题,如果bar()不在那儿,但出于某种原因要它仍然可以在x++
和console.log(x);
语句之间运行,该怎么实现呢?
此时就可以使用ES6的生成器来实现这样的功能:
var x = 1;function *foo(){
x++;
yield;
console.log(x);
}
function bar(){
x++;
}
// 构造一个迭代器it来控制生成器*foo()
var it = foo();
//这里启动 *foo()生成器
it.next();console.log(x); // 2
bar();
console.log(x); // 3
it.next();
首先注意上例中的生成器格式 function *foo(){}
,通常这个*
号位置还有其他两种,它们都是等价的:function* foo(){}
和function*foo(){}
。而这里采用第一种格式是因为使用*foo(){}
来引用生成器时会比较一致,如果只是foo()
的形式,就不容易搞清指的是生成器还是常规函数。所以这只是一种风格偏好。
现在在描述下上例的运行过程:
var it = foo();
运算并没有执行生成器 *foo()
,而只是构造了一个迭代器(iterator),这个迭代器会控制它的执行。it.next();
启动了生成器*foo()
,并运行了*foo()
第一行的代码x++
。*foo()
在yield
语句处暂停,在这个点位上第一个it.next()
调用结束。此时*foo()
仍然是运行且活跃的,但函数处理暂停状态。bar()
,它通过x++
两次递增x的值it.next()
调用从暂停处恢复了生成器*foo()
的执行,并运行console.log()
语句,这条语句使用当前x的值3。所以可以这样理解:foo()启动了,但是没有完整运行,它在yield处暂停了。暂停其间可以执行其他需要的代码,执行完后再调用迭代器it的next()方法恢复生成器foo()并让它继续执行到结束。
所以生成器就是一类特殊的函数,可以一次或多次启动和停止,并不一定非得到完成。
它将用于构建以生成器作为异步流程控制的代码模式的基础构件之一。