生成器的一种有趣用法是作为一种产生值的方式。而这也是“生成器”这个名称的最初使用场景。前面说过生成器函数每次调用都会创建一个迭代器实例。这个迭代器实例有next()方法,与生成器函数中的yield
关键字组合可以完成消息传递。
那怎么理解这个生成器和迭代器呢?从字面意思理解,可以理解成生成器是值的生产者,而迭代器是值的索取者。生成器函数调用产生迭代器,迭代器用next()方法来执行生成器函数。
这里面需要注意一个关键点,迭代器每次next()要想实现当前值与前面一个值有特定的关系,就需要生成器能保持状态来记住其生成的最后一个值。
首先可以实现一个使用函数闭包的版本:
var clourseSomething = (function(){
var nextVal;
return function(){
if(nextVal === undefined){
nextVal = 1;
}else {
nextVal = ( nextVal * 3 ) + 6;
}
return nextVal;
}
})()
clourseSomething(); // 1
clourseSomething(); // 9
clourseSomething(); // 33
clourseSomething(); // 105
使用闭包的话,从执行粒度上看函数完成一次执行时就已经计算出下一个值,但实际上按照正常思路,应该是希望直到下一次clourseSomething()调用发生时才计算下一个值(即nextVal)。否则通常来说对更持久化或比起简单数字资源更受限的生产者来说,这可能是资源泄露的一种设计。唔,这也是闭包所容易为人诟病的一点。
这个时候就可以采用一种常见的设计模式,迭代器模式。迭代器是一个定义良好的接口,用于从一个生产者一步步得到一系列值,就是每次想要从生产者得到下一个值时调用next()。
var something = (function () {
var nextVal;
return {
//for..of..循环需要
[Symbol.iterator]: function(){ return this; },
//标准迭代器接口方法
next: function () {
if(nextVal === undefined){
nextVal = 1;
}else {
nextVal = ( nextVal*3 ) + 10;
}
return {done: false, value: nextVal}
}
}
})()
console.log(something.next().value); // 1
console.log(something.next().value); // 13
console.log(something.next().value); // 49
next()调用返回一个对象。这个对象有两个属性:done是一个布尔值,标识迭代器的完成状态;value中放置迭代值。
ES6中新增了for...of...
方法,这意味着可以通过原生语法循环自动迭代标准迭代器(接上例somthing):
for(var v of something){
console.log(v);
//不要死循环
if(v > 500){
break;
}
}
// 1 13 49 157 481 1453
for..of
循环在每次迭代中自动调用next(),它不会向next()中传入任何值,并且会在接收done:true
时停止。上例中迭代器something总是返回done:false
,这个for…of循环会永远运行下去,所以在测试循环里放入break条件。
除了构造自己的迭代器,许多javascript的内建数据结构(从ES6开始),比如array,也有默认的迭代器:
var arr = [10, 2, 3, 4, 5];
for(var v of arr){
console.log(v);
}
// 10 2 3 4 5
for...of
循环向arr请求它的迭代器,并自动使用这个迭代器来迭代遍历arr的值。
可能有朋友对迭代器something中的Symbol.iterator
属性不是很了解,它是迭代器的定义标识,是for...of..
工作的基础,具体将在下篇解释。
喜欢本文请扫下方二维码,关注微信公众号: 前端小二,查看更多我写的文章哦,多谢支持。