本章内容
- 理解迭代
- 迭代器模式
- 生成器
迭代的英文“iteration”源自拉丁文itero,意思是“重复”或“再来”。 在软件开发领域,“迭代”的意思是按照顺序反复多次执行一段程序, 通常会有明确的终止条件。
ECMAScript 6规范新增了两个高级特性: 迭代器和生成器。使用这两个特性,能够更清晰、高效、方便地实现 迭代。
理解迭代
在JavaScript中,计数循环就是一种最简单的迭代:
for (let i = 1; i <= 10; ++i) {
console.log(i);
}
循环是迭代机制的基础,这是因为它可以指定迭代的次数,以及 每次迭代要执行什么操作。每次循环都会在下一次迭代开始之前完 成,而每次迭代的顺序都是事先定义好的。
迭代会在一个有序集合上进行。(“有序”可以理解为集合中所有 项都可以按照既定的顺序被遍历到,特别是开始和结束项有明确的定 义。)数组是JavaScript中有序集合的最典型例子。
let collection = ['foo','bar','baz'];
for (let index = 0; index < collection.length;++index) {
console.log(collection[index]);
}
因为数组有已知的长度,且数组每一项都可以通过索引获取,所 以整个数组可以通过递增索引来遍历。 由于如下原因,通过这种循环来执行例程并不理想
- 迭代之前需要事先知道如何使用数据结构。数组中的每一项都只 能先通过引用取得数组对象,然后再通过 [] 操作符取得特定索 引位置上的项。这种情况并不适用于所有数据结构。
- 遍历顺序并不是数据结构固有的。通过递增索引来访问数据是特 定于数组类型的方式,并不适用于其他具有隐式顺序的数据结构。
ES5新增了 Array.prototype.forEach() 方法,向通用迭代 需求迈进了一步(但仍然不够理想):
let collection = ['foo','bar','baz'];
collection.forEach((item) => console.log(item));
// foo
// bar
// baz
这个方法解决了单独记录索引和通过数组对象取得值的问题。不 过,没有办法标识迭代何时终止。因此这个方法只适用于数组,而且 回调结构也比较笨拙。
在ECMAScript较早的版本中,执行迭代必须使用循环或其他辅助 结构。随着代码量增加,代码会变得越发混乱。很多语言都通过原生 语言结构解决了这个问题,开发者无须事先知道如何迭代就能实现迭 代操作。这个解决方案就是迭代器模式。Python、Java、C++,还有其 他很多语言都对这个模式提供了完备的支持。
JavaScript在ECMAScript 6以后也支持了迭代器模式。
迭代器模式
迭代器模式(特别是在ECMAScript这个语境下)描述了一个方 案,即可以把有些结构称为“可迭代对象”(iterable),因为它们实现 了正式的 Iterable 接口,而且可以通过迭代器 Iterator 消费。
可迭代对象是一种抽象的说法。基本上,可以把可迭代对象理解 成数组或集合这样的集合类型的对象。它们包含的元素都是有限的, 而且都具有无歧义的遍历顺序:
// 数组的元素是有限的
// 递增索引可以按序访问每个元素
let arr = [3, 1, 4];
// 集合的元素是有限的
// 可以按插入顺序访问每个元素
let set = new Set().add(3).add(1).add(4);
可迭代协议
实现 Iterable 接口(可迭代协议)要求同时具备两种能力: 支持迭代的自我识别能力和创建实现 Iterator 接口的对象的能 力。在ECMAScript中,这意味着必须暴露一个属性作为“默认迭代 器”,而且这个属性必须使用特殊的 Symbol.iterator
作为键。这 个默认迭代器属性必须引用一个迭代器工厂函数,调用这个工厂函数 必须返回一个新迭代器。
很多内置类型都实现了 Iterable 接口:
- 字符串
- 数组
- 映射
- 集合
- arguments 对象
- NodeList 等DOM集合类型
检查是否存在默认迭代器属性可以暴露这个工厂函数:
实际写代码过程中,不需要显式调用这个工厂函数来生成迭代 器。实现可迭代协议的所有类型都会自动兼容接收可迭代对象的任何 语言特性。接收可迭代对象的原生语言特性包括:
迭代器协议
迭代器是一种一次性使用的对象,用于迭代与其关联的可迭代对 象。迭代器API使用 next() 方法在可迭代对象中遍历数据。标签
每次成 功调用 next() ,都会返回一个 IteratorResult 对象,其中包 含迭代器返回的下一个值。
若不调用 next() ,则无法知道迭代器的 当前位置。 next() 方法返回的迭代器对象 IteratorReault 包含两个 属性: done 和 value 。
done 是一个布尔值,表示是否还可以再 次调用 next() 取得下一个值, done: true 状态称为“耗尽”。
value 包含可迭代对象的下一个值 ( done 为 false ),或者 undefined ( done 为 true )。 可以通过以下简单的数组来演示: