Skip to content

迭代器与生成器

实现了正式的Iterable接口,就可以通过迭代器Iterator消费

// 这个类实现了可迭代接口(Iterable)
// 调用默认的迭代器工厂函数会返回
// 一个实现迭代器接口(Iterator)的迭代器对象
class Foo { 
    [Symbol.iterator]() {     
        return {       
            next() {         
                return { done: false, value: 'foo' };       
            }     
        }   
    } 
} 
let f = new Foo();

自定义迭代器

可选的return()方法用于指定在迭代器提前关闭时执行的逻辑。

for-of循环通过break、continue、return或throw提前退出

class Counter { 
    constructor(limit) { 
        this.limit = limit; 
    } 
    [Symbol.iterator] () { 
        let count = 1, limit = this.limit; 
        return { 
            next () { 
                if (count <= limit) { 
                    return { done: false, value: count++ }; 
                } else { 
                    return { done: true, value: undefined }; 
                } 
            },
            return() { 
                console.log('Exiting early'); 
                return { done: true }; 
            }  
        }; 
    } 
} 
let counter = new Counter(3); 
for (let i of counter) { console.log(i); } // 1 // 2 // 3
如果迭代器没有关闭,则还可以继续从上次离开的地方继续迭代。比如,数组的迭代器就是不能关闭的
let a = [1, 2, 3, 4, 5]; 
let iter = a[Symbol.iterator](); 
for (let i of iter) {   
    console.log(i);    
    if (i > 2) {     
        break   
    } 
} // 1 // 2 // 3 
for (let i of iter) {   
    console.log(i); 
} 
// 4 // 5

因为return()方法是可选的,所以并非所有迭代器都是可关闭的。要知道某个迭代器是否可关闭,可以测试这个迭代器实例的return属性是不是函数对象。不过,仅仅给一个不可关闭的迭代器增加这个方法并不能让它变成可关闭的。这是因为调用return()不会强制迭代器进入关闭状态。即便如此,return()方法还是会被调用。

生成器

有在一个函数块内暂停和恢复代码执行的能力,使用生成器可以自定义迭代器和实现协程。 生成器的形式是一个函数,函数名称前面加一个星号(*)表示它是一个生成器。 只要是可以定义函数的地方,就可以定义生成器。 // 生成器函数声明

function* generatorFn() {}

成器对象一开始处于暂停执行(suspended)的状态。 与迭代器相似,生成器对象也实现了Iterator接口,因此具有next()方法。调用这个方法会让生成器开始或恢复执行。

通过yield中断执行

生成器函数在遇到yield关键字之前会正常执行。遇到这个关键字后,执行会停止,函数作用域的状态会被保留 停止执行的生成器函数只能通过在生成器对象上调用next()方法来恢复执行 此时的yield关键字有点像函数的中间返回语句,它生成的值会出现在next()方法返回的对象里 通过yield关键字退出的生成器函数会处在done: false状态;通过return关键字退出的生成器函数会处于done: true状态。

function* generatorFn() {  
    yield 'foo'; 
    yield 'bar';   
    return 'baz'; 
} 
let generatorObject = generatorFn(); 
console.log(generatorObject.next());  // { done: false, value: 'foo' } 
console.log(generatorObject.next());  // { done: false, value: 'bar' } 
console.log(generatorObject.next());  // { done: true, value: 'baz' }

使用yield实现输入和输出

除了可以作为函数的中间返回语句使用,yield关键字还可以作为函数的中间参数使用。上一次让生成器函数暂停的yield关键字会接收到传给next()方法的第一个值。这里有个地方不太好理解——第一次调用next()传入的值不会被使用,因为这一次调用是为了开始执行生成器函数:

function* generatorFn(initial) {   
    console.log(initial);    
    console.log(yield);   
    console.log(yield); 
} 
let generatorObject = generatorFn('foo'); 
generatorObject.next('bar');  // foo 
generatorObject.next('baz');  // baz 
generatorObject.next('qux');  // qux

yield关键字可以同时用于输入和输出,如下例所示:

function* generatorFn() {  
    return yield 'foo'; 
} 
let generatorObject = generatorFn(); 
console.log(generatorObject.next());       // { done: false, value: 'foo'} 
console.log(generatorObject.next('bar'));  // { done: true, value: 'bar' }

产生可迭代对象

使用星号增强yield的行为,让它能够迭代一个可迭代对象,从而一次产出一个值

function* generatorFn() { 
    yield* [1, 2, 3]; 
} 
let generatorObject = generatorFn(); 
for (const x of generatorFn()) {   
    console.log(x); 
} // 1 // 2

使用yield*实现递归算法

yield*最有用的地方是实现递归操作,此时生成器可以产生自身。看下面的例子:

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

提前终止生成器

可选的return()方法用于提前终止迭代器。生成器对象除了有这两个方法,还有第三个方法:throw() return不可恢复 throw可恢复