在介绍生成器之前,迭代器的一些知识是与生成器相互关联的。生成器官方的定义是:一种返回迭代器的函数,通过function关键字后的星号(*)来表示,函数中通常会用到新的关键在yield。NOTE:不能用箭头函数来创建生成器。举个官方的例子:
function *createIterator(){
yield 1;
}
因为本质上来讲,生成器终究是一个函数,所以其调用的方式和普通函数调用的方式相同。有区别的在于,在调用生成器函数后,需要继续手动的进行其next方法。而这next的方法是来自于迭代器中,因为同定义阐述,在调用生成器函数后,此时就转换成了迭代器。简单的介绍下next方法,每次调用next都返回一个结果对象,这对象中包含两个属性,一个是value(表示返回的值),一个是done(是一个布尔值),例子:{ value: 1, done:false}。如果在最后一个值后返回后再调用next方法, 则返回 { value:undefined,done:true}。拿上面的生成器举例子:
var iterator = createIterator();
console.log(iterator.next()) // {value: 1, done: false}
console.log(iterator.next()) // { value: undefined, done: true}
next方法先讲一半到这,后面还有结合yield会发现有趣的事。提到yield这个关键字,首先还是介绍一下它,没有特别官方的解释,大概的意思是,没当执行完一条yield语句时,函数就会自动暂停执行,而且yield可以返回任何值或者是表达式。当然最重要的一点就是使用环境,它只可以在生成器内部使用。
上面提到了在执行yield时,函数会暂停执行,那么什么时候才会继续呢?这不,next方法又重新登场了,在使用next方法时,可以继续当前函数,当然了再遇到下一个yield关键字时又会被暂停执行。这也就是为什么,在生成器中,next方法永远会比yield关键字多使用一次的原因(第一次用于开始迭代器)。
有趣的还没结束,当yield停止函数时,跟在yield后面的表达时也好,其他值也好,都会被调用者接收到,这也是上面第一次调用next的时候,value等于1而不等于其他值。这时候next优秀的地方又来了,你以为只有简单的继续函数吗,它可不服,next方法还可以接受一个参数用于代替yield后面的值被表达式接收。
function *createIterator(){
var newNum = yield 1;
console.log(newNum); //4
}
var iterator = createIterator();
iterator.next().value // 1
iterator.next(4)
另外需要注意的是,可迭代对象具有Symbol.iterator属性,这是一种与迭代器密切相关的对象(从命名也能看出端倪)。在ES6中,所有的集合对象(数组,Set集合以及Map集合)和字符串都是可迭代的对象,这些对象中都有默认的迭代器。而for...of循环则需要用到可迭代对象的这些功能,这也是为什么普通的对象类型无法使用这个for...of循环的原因。for...of循环每执行一次都会调用可迭代对象的next方法,是因为可以找到可迭代对象的Symbol.iterator属性获取迭代器(这一个过程是在JS引擎背后完成)。当结果对象的done属性值为true时退出,这是为何我们遍历循环时候输出了最后一个值就不再继续输出了一样。
默认情况,普通对象都是不可迭代的,但如果给Symbol.iterator属性添加一个生成器,则可以变为可迭代对象,来自官方的一个例子:
let collection = {
items: [],
*[Symbol.iterator]() {
for (let item of this.items){
yield item;
}
}
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let k of collection){
console.log(k);
}
// 1
// 2
//3
====================
文章中很多概念问题是来自于深入理解ES6。
在《你不知道的javascript(中)》中发现了一个关于多个迭代器交叉运行的有趣例子。
var a = 1;
var b = 2;
function *foo(){
a++;
yield;
b = b * a;
a = (yield b) + 3;
}
function *bar(){
b--;
yield;
a = (yield 8) + b;
b = a* (yield 2);
}
function step(gen) {
var it = gen();
var last;
return function(){
//不管yield出来的是什么值,下一次都把它原样穿回去。
last = it.next(last).value;
}
}
var s1 = step( foo );
var s2 = step( bar );
s2();
s2();
s1();
s2();
s1();
s1();
s2();
console.log(a, b); // 12 18