<摘录> ES6中的迭代器和生成器

迭代器

迭代器是ES6标准制定的被设计专用于迭代的对象,带有特定接口。所有的迭代器对象都拥有next()方法,会返回一个结果对象。该结果对象有两个属性:对应下一个值的value,以及一个布尔类型的done,其值为true时表示没有更多值可供使用。迭代器持有一个指向集合位置的内部指针,每当调用了next()方法,迭代器就会返回相应的下一个值。

迭代器示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function createIterator(items) {
var i = 0;
return {
next: function() {
var done = (i >= items.length);
var value = !done ? items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}

var 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 }"

生成器

按照ES6标准书写迭代器,有一些复杂,不过好在ES6同时引入了生成器,可以让创建迭代器变得简单。

生成器(generator) 是能返回一个迭代器的函数。生成器函数由放在function关键字之后的一个星号(*)来表示,并能使用新的yield关键字。生成器函数是 ES6 的一个重要特性,并且因为它就是函数,所以能被用于所有可用函数的位置。

1
2
3
4
5
6
7
8
9
10
11
// 生成器
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
// 生成器能像正规函数那样被调用,但会返回一个迭代器
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

生成器函数最有意思的方面可能就是它们会在每个yield语句后停止执行。例如,此代码中yield 1执行后,该函数将不会再执行任何操作,直到迭代器的next()方法被调用,此时才继续执行yield 2

可迭代对象与for-of循环

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

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

for-of 循环在循环每次执行时会调用可迭代对象的next()方法,并将结果对象的value值存储在一个变量上。循环过程会持续到结果对象的done属性变成true为止。此处有个范例:

1
2
3
4
5
let values = [1, 2, 3];
for (let num of values) {
console.log(num);
}
// 输出1 2 3

这个 for-of 循环首先调用了values数组的Symbol.iterator方法,获取了一个迭代器(对Symbol.iterator的调用发生在 JS 引擎后台)。接下来iterator.next()被调用,迭代器结果对象的value属性被读出并放入了num变量。num变量的值开始为 1 ,接下来是 2 ,最后变成 3 。当结果对象的done变成true,循环就退出了,因此 num绝不会被赋值为undefined

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}

// 输出: 1 2 3

摘录来源

  1. Nicholas C. Zakas, Understanding ECMAScript 6 中文翻译 http://www.dfhuo.com/es6.pdf