Published on

迭代器和生成器

Authors
  • avatar
    Name
    李丹秋
    Twitter

迭代器

定义

实现 Iterable 接口(可迭代协议)要求同时具备两种能力:支持迭代的自我识别能力和创建实现 Iterator 接口的对象的能力。在 ECMAScript 中,这意味着必须暴露一个属性作为“默认迭代器”,而 且这个属性必须使用特殊的 Symbol.iterator 作为键。这个默认迭代器属性必须引用一个迭代器工厂 函数,调用这个工厂函数必须返回一个新迭代器。 很多内置类型都实现了 Iterable 接口:

  • 字符串
  • 数组
  • 映射
  • 集合
  • arguments对象
  • NodeList等DOM 合类型 使用obj[Symbol.Iterable]来检验一个对象是否具备迭代器能力

实际写代码过程中,不需要显式调用这个工厂函数来生成迭代器。实现可迭代协议的所有类型都会 自动兼容接收可迭代对象的任何语言特性。接收可迭代对象的原生语言特性包括

  • for-of 循环
  • 数组解构
  • 扩展操作符
  • Array.from()
  • 创建集合 -创建映射
  • Promise.all()接收由期约组成的可迭代对象
  • Promise.race()接收由期约组成的可迭代对象
  • yield*操作符,在生成器中使用

实现一个迭代器

class Counter {
    constructor(limit) {
        this.limit = limit;
    }
    [Symbol.iterator]() {
        let count = 1;
        const limit = this.limit;
        return {
            next() {
                if (count <= limit) {
                    return { done: false, value: count++ };
                } else {
                    return { done: true };
                }
            },
            return() {
                console.log('Exiting early');
                return { done: true };
            }
        };
    }
}

生成器

生成器是 ECMAScript 6 新增的一个极为灵活的结构,拥有在一个函数块内暂停和恢复代码执行的 能力。这种新能力具有深远的影响,比如,使用生成器可以自定义迭代器和实现协程。生成器的形式是一个函数,函数名称前面加一个星号(*)表示它是一个生成器。只要是可以定义 函数的地方,就可以定义生成器。

function* generatorFn() {} 
// 生成器函数表达式
let generatorFn = function* () {} 
// 作为对象字面量方法的生成器函数
let foo = { 
 * generatorFn() {} 
} 
// 作为类实例方法的生成器函数
class Foo { 
 * generatorFn() {} 
} 
// 作为类静态方法的生成器函数
class Bar { 
 static * generatorFn() {} 
}

箭头函数不能定义生成器

生成器的独特之处在于支持 yield 关键字,这个关键字能够暂停执行生成器函数。使用 yield 关键字还可以通过 next()方法接收输入和产生输出。在加上星号之后,yield 关键字可以将跟在它后面的可迭代对象序列化为一连串值。 下面两个生成器函数的行为是等价的:

function * generatorFnA() {
    for (const x of [1, 2, 3]) {
        yield x;
    }
}
for (const x of generatorFnA()) {
    console.log(x);
}

function * generatorFnB() {
    yield * [1, 2, 3];
}
for (const x of generatorFnB()) {
    console.log(x);
}

生成器作为默认迭代器

因为生成器对象实现了 Iterable 接口,而且生成器函数和默认迭代器被调用之后都产生迭代器, 所以生成器格外适合作为默认迭代器。下面是一个简单的例子,这个类的默认迭代器可以用一行代码产 出类的内容:

class Foo {
    constructor() {
        this.values = [1, 2, 3];
    }
    * [Symbol.iterator]() {
        yield * this.values;
    }
}

为什么 React Fiber 不使用生成器来实现