js迭代器和生成器
迭代器
在很多语言中都有迭代器,例如QList<int>::iterator iter = list.begin();
while(iter != list.end())
{
iter = iter->next();
}
在js中
let set = new Set().add(3).add(1).add(4)
通过上面的例子可以看出,迭代器的关键是不断返回一个相同类型的不同实例。在js中,实现Iterable接口就可以认为是一个迭代器。迭代器是按需创建的一次性对象,每个迭代器都会关联一个可迭代对象,而迭代器会暴露迭代其关联对象的API(例如next)
实现Iterator接口需要实现两种能力: 自我识别能力和创建实现Iterator接口的对象的能力。这意味着需要暴露一个属性作为默认迭代器,并且这个属性还要以Symbol.iterator作为键。默认迭代器必须引用一个迭代器工厂函数,调用这个工厂函数必须返回一个新迭代器。
常见的实现了Iterator接口的类:字符串、数组、map、set、arguments、NodeList
可以使用for-of循环、数组解构、Array.from()等对迭代器进行遍历
自定义迭代器
class Counter |
上述类实现了迭代器,首先我们可以使用iterator获得迭代类,并且next可以用来返回下一个对象。
但是这种方法并不理想,因为每个实例只能够被迭代一次。为了可以迭代多次,可以创建一个计数器
class Counter |
在这种情况下,每次调用Symbol.iterator时数据总是会变为初始状态。
提前终止
可以增加return()来提前终止迭代器。在for-of循环中,可以通过break、continue、return或throw提前退出
class Counter |
生成器
构造: 在函数名称前加一个星号(*)来表示它是生成器
function* generatorFn(){} |
yield
调用生成器会产生一个生成器对象。这个对象实现了Iterator。每次调用next()都会跳转到下一个yieldfunction* generatorFn()
{
yield 'foo';
yield 'bar';
return 'baz';
}
let generator = generatorFn();
console.log(generator.next());//foo
console.log(generator.next());//bar
console.log(generator.next());//baz
每次执行到yield后就输出并停止执行,但是它的状态时保存的,下一次可以继续使用上一次yield中的变量。
碰到yield,next返回的done是false如果碰到return则done为true。即return终止迭代。
yield只有在生成器函数内部定义中有效,诸如闭包,内部函数等使用都会报错function* generator()
{
function a()
{
yield;
}
}
function* gene()
{
(()=>{yield;})();
}
上面两种都会报错
yield接收输入
yield除了作为输出返回值使用,他还可以接收传给next的第一个值。但是第一次调用next输入的参数是无效的,这一次调用是为了启动生成器函数。function* gene(initial)
{
console.log(initial);
console.log(yield);
console.log(yield);
}
let generator = gene('foo');
generator('bar');//foo,是因为第一次执行到yield就停止,并且没有输入任何参数
generator('baz');//baz,第一个yield接收这个参数
generator('qux');//qux
yield可以同时用于输入和输出function* generator()
{
return yield 'foo';
}
let gene = generator();
gene.next();//foo,做输出用
gene.next("bar");//bar,yield接收输入并return
yield与*配合
yield后面可以接*来多次返回值,例如function* gene()
{
yield* [1, 2];
yield* [3, 4];
yield* [5, 6];
}
for(const x of gene())
{
console.log(x);//1, 2, 3, 4, 5, 6
}
可以使用这个很方便的实现递归操作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
}
过程为
nTimes(2);
nTimes(1);
nTimes(0);
0
1
2
也可以使用这种方法为类添加迭代器class Foo
{
constructor()
{
this.values = [1, 2, 3];
}
*[Symbol.iterator]()
{
yield* this.values;
}
}
提前终止生成器
所有的生成器都有return()方法,他会直接让生成器进入关闭状态,并且之后都无法恢复。function* generatorFn()
{
for(const x of [1, 2, 3])
{
yield x;
}
}
const g = generatorFn();
console.log(g.next());//{done:false, value: 1}
console.log(g.return(4));//{done:true, value: 4}
console.log(g);//generatorFn{<closed>}
此外,还可以通过throw将错误抛出,如果错误未被处理,生成器将关闭.但是如果生成器函数内部处理了这个错误,那么生成器不会关闭function gene()
{
for(const x of [1, 2, 3])
{
try
{
yield x;
}
catch(e){}
}
}
const g = gene();
console.log(g.next());//1
g.throw('foo');产生错误但是生成器内部接住了
console.log(g.next());//3