什么是迭代器?

迭代器是被设计专用于迭代的对象,带有特定接口。所有的迭代器对象都拥有 next() 方
法,会返回一个结果对象。该结果对象有两个属性:对应下一个值的 value ,以及一个布尔
类型的 done ,其值为 true 时表示没有更多值可供使用。迭代器持有一个指向集合位置的
内部指针,每当调用了 next() 方法,迭代器就会返回相应的下一个值。
若你在最后一个值返回后再调用 next() ,所返回的 done 属性值会是 true ,并且
value 属性值会是迭代器自身的返回值( return value ,即使用 return 语句明确返回的
值)。该“返回值”不是原数据集的一部分,却会成为相关数据的最后一个片段,或在迭代器未
提供返回值的时候使用 undefined 。迭代器自身的返回值类似于函数的返回值,是向调用者
返回信息的最后手段。

什么是生成器?

生成器( generator )是能返回一个迭代器的函数。生成器函数由放在 function 关键字之
后的一个星号( * )来表示,并能使用新的 yield 关键字。将星号紧跟在 function 关键
字之后,或是在中间留出空格,都是没问题的。

// 生成器
function *createIterator(items) {
    for (let i = 0; i < items.length; i++) {
        yield items[i];
    }
}
// 迭代器
let iterator = createIterator([1, 2, 3]);
// 迭代操作
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"

生成器函数表达式

你可以使用函数表达式来创建一个生成器,只要在 function 关键字与圆括号之间使用一个
星号( * )即可。

例子:

// 生成器函数表达式
let createIterator = function *(items) {
    for (let i = 0; i < items.length; i++) {
        yield items[i];
    }
};
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"

生成器对象方法

//方式一
var o = {
    createIterator: function *(items) {
        for (let i = 0; i < items.length; i++) {
        yield items[i];
        }
    }
};
// 方式二
var o = {
    *createIterator(items) {
        for (let i = 0; i < items.length; i++) {
            yield items[i];
        }
    }
};
let iterator = o.createIterator([1, 2, 3]);

可迭代对象与 for-of 循环

与迭代器紧密相关的是,可迭代对象( iterable )是包含 Symbol.iterator 属性的对象。这
个 Symbol.iterator 知名符号定义了为指定对象返回迭代器的函数。在 ES6 中,所有的集合
对象(数组、 Set 与 Map )以及字符串都是可迭代对象,因此它们都被指定了默认的迭代
器。可迭代对象被设计用于与 ES 新增的 for-of 循环配合使用。

生成器创建的所有迭代器都是可迭代对象,因为生成器默认就会为 Symbol.iterator 属
性赋值。

访问默认迭代器

你可以使用 Symbol.iterator 来访问对象上的默认迭代器,就像这样

let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

创建可迭代对象

开发者自定义对象默认情况下不是可迭代对象,但你可以创建一个包含生成器的
Symbol.iterator 属性,让它们成为可迭代对象。例如:

let collection = {
    items: [],
    *[Symbol.iterator]() {
        for (let item of this.items) {
            yield item;
        }
    }
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
    console.log(x);
}

集合的迭代器

ES6 具有三种集合对象类型:数组、 Map 与Set。这三种类型都拥有如下的迭代器,有助于
探索它们的内容:

  • entries() :返回一个包含键值对的迭代器;
  • values() :返回一个包含集合中的值的迭代器;
  • keys() :返回一个包含集合中的键的迭代器。

传递参数给迭代器

当一个参数被传递给next() 方法时,该参数就会成为生成器内部 yield 语句的值。

例如:

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2; // 4 + 2
    yield second + 3; // 5 + 3
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.next(5)); // "{ value: 8, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

对于 next() 的首次调用是一个特殊情况,传给它的任意参数都会被忽略。由于传递给
next() 的参数会成为 yield 语句的值,该 yield 语句指的是上次生成器中断执行处的语
句;而 next() 方法第一次被调用时,生成器函数才刚刚开始执行,没有所谓的“上一次中断处的 yield 语句”可供赋值。因此在第一次调用next()时,不存在任何向其传递参数的理
由。

生成器的 Return 语句

由于生成器是函数,你可以在它内部使用 return 语句,既可以让生成器早一点退出执行,
也可以指定在 next() 方法最后一次调用时的返回值。在本章大多数例子中,对迭代器上的
next() 的最后一次调用都返回了 undefined ,但你还可以像在其他函数中那样,使用
return 来指定另一个返回值。在生成器内, return 表明所有的处理已完成,因此 done
属性会被设为 true ,而如果提供了返回值,就会被用于 value 字段。此处有个例子,单
纯使用 return 让生成器更早返回:

function *createIterator() {
    yield 1;
    return;
    yield 2;
    yield 3;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

生成器委托

在某些情况下,将两个迭代器的值合并器一起会更有用。生成器可以用星号( * )配合
yield 这一特殊形式来委托其他的迭代器。正如生成器的定义,星号出现在何处是不重要
的,只要落在 yield 关键字与生成器函数名之间即可。此处有个范例:

function *createNumberIterator() {
    yield 1;
    yield 2;
}
function *createColorIterator() {
    yield "red";
    yield "green";
}
function *createCombinedIterator() {
    yield *createNumberIterator();
    yield *createColorIterator();
    yield true;
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: "red", done: false }"
console.log(iterator.next()); // "{ value: "green", done: false }"
console.log(iterator.next()); // "{ value: true, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

结语

迭代器是 ES6 重要的一部分,并且也是语言若干关键元素的根源。在表面上,迭代器提供了一种使用简单接口来返回值序列的简单方式。然而,在ES6中使用迭代器,还有着种种更加复杂的方式。

Symbol.iterator 符号被用于定义对象的默认迭代器。内置对象与开发者自定义对象都可以使用这个符号,以提供一个能返回迭代器的方法。当Symbol.iterator在一个对象上存在时,该对象就会被认为是可迭代对象。

for-of 循环在循环中使用可迭代对象来返回一系列数据。与使用传统 for 循环进行迭代相
比,使用 for-of 要容易得多,因为你不再需要追踪计数器并控制循环何时结束。 for-of
循环会自动从迭代器中读取所有数据,直到没有更多数据为止,然后退出循环。

为了让 for-of 更易使用,ES6中的许多类型都具有默认的迭代器。所有的集合类型(也就是数组、 Map 与 Set )都具有迭代器,让它们的内容更易被访问。字符串同样具有一个默认迭代器,能更加轻易地迭代字符串中的字符(而非码元)。

扩展运算符能操作任意的可迭代对象,同时也能更简单地将可迭代对象转换为数组。转换工
作会从一个迭代器中读取数据,并将它们依次插入数组。

生成器是一个特殊的函数,可以在被调用时自动创建一个迭代器。生成器的定义用一个星号
( * )来表示,使用 yield 关键字能指明在每次成功的 next() 方法调用时应当返回什么
值。

生成器委托促进了对迭代器行为的良好封装,让你能将已有的生成器重用在新的生成器中。
通过调用 yield * 而非 yield ,你就能把已有生成器用在其他生成器内部。这种处理方式
能创建一个从多个迭代器中返回值的新迭代器。

生成器与迭代器最有趣、最令人激动的方面,或许就是可能创建外观清晰的异步操作代码。
你不必到处使用回调函数,而是可以建立貌似同步的代码,但实际上却使用 yield 来等待异步操作结束。