迭代器

在很多语言中都有迭代器,例如

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
{
constructor(limit)
{
this.count = 1;
this.limit = limit;
}

next()
{
if(this.count <= this.limit)
{
return {done: false, value: this.count++};
}
else
{
return {done:true, value: undefined};
}
}

[Symbol.iterator]()
{
return this;
}
}

上述类实现了迭代器,首先我们可以使用iterator获得迭代类,并且next可以用来返回下一个对象。

但是这种方法并不理想,因为每个实例只能够被迭代一次。为了可以迭代多次,可以创建一个计数器

class Counter
{
constructor(limit)
{
this.limit = limit;
}

[Symbol.iterator]()
{
let count = 1;
let limit = this.limit;
return
{
next()
{
if(count <= limit)
{
return {done: false, value: cont++};
}
else
{
return {done: true, value: undefined};
}
}
};
}
}

在这种情况下,每次调用Symbol.iterator时数据总是会变为初始状态。

提前终止

可以增加return()来提前终止迭代器。在for-of循环中,可以通过break、continue、return或throw提前退出

class Counter
{
constructor(limit)
{
this.limit = limit;
}

[Symbol.iterator]()
{
let count = 1;
let limit = this.limit;
return
{
next()
{
if(count <= limit)
{
return {done:false, value: count++};
}
else
{
return {done:true};
}
},
return()
{
return {done:true};
}
}
}
}

生成器

构造: 在函数名称前加一个星号(*)来表示它是生成器

function* generatorFn(){}

let generatorFn = function*(){}

let foo =
{
* generatorFn(){}
}

class Foo
{
* generatorFn(){}
}

class Bar
{
static * generatorFn(){}
}
*号两边空格数目不影响解析

yield

调用生成器会产生一个生成器对象。这个对象实现了Iterator。每次调用next()都会跳转到下一个yield

function* 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