在软件开发领域,“迭代” 的意思是按照顺序反复多次执行一段程序,通常会有明确的终止条件。
迭代器(Iterator)是一种迭代的机制,为各种不同的数据结构提供统一的访问机制。任何数据结构只要内部有 Iterator 接口,就可以完成依次迭代操作。
理解迭代
循环是迭代机制的基础,这是因为它可以指定迭代的次数,以及每次迭代要执行什么操作。
迭代会在一个有序集合上进行。数组是 JavaScript 中有序集合的最典型例子。
由于如下原因,通过这种循环来执行例程并不理想。
- 迭代之前需要事先知道如何使用数据结构。
- 遍历顺序并不是数据结构固有的。
在 ECMAScript 较早的版本中,执行迭代必须使用循环或其他辅助结构。不同的数据结果需要开发者写不同的循环控制逻辑,这就需要开发人员对需要迭代的数据结构非常熟悉。
迭代器模式
开发者无须事先知道如何迭代就能实现迭代操作。这个解决方案就是迭代器模式。我们可以把有些结构称为“可迭代对象”(iterable),因为它们实现了正式的 Iterable 接口,而且可以通过迭代器 Iterator 消费。
任何实现 Iterable 接口的数据结构都可以被实现 Iterator 接口的结构“消费”(consume)。迭代器(iterator)是按需创建的一次性对象。每个迭代器都会关联一个可迭代对象,而迭代器会暴露迭代其关联可迭代对象的 API。迭代器无须了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值。这种概念上的分离正是 Iterable 和 Iterator 的强大之处。
可迭代协议
在ECMAScript 中,暴露了一个键为Symbol.iterator的属性作为“默认迭代器”,这个属性引用了一个迭代器工厂函数,调用这个工厂函数会返回一个新迭代器。
很多内置类型都实现了 Iterable 接口
- 字符串
- 数组
- 映射
- 集合
- arguments 对象
- NodeList 等 DOM 集合类型
- 没有对象!没有对象!没有对象!
let obj = {}
console.log(obj[Symbol.iterator]); // undefined
// 迭代器工厂函数
let arr = ['a', 'b', 'c']; // ƒ values() { [native code] }
// 调用这个工厂函数会生成一个迭代器
console.log(arr[Symbol.iterator]()) // Array Iterator {}
接收可迭代对象的原生语言特性包括:
- for-of 循环
- 数组解构
- 扩展操作符
- Array.from()
- 创建集合
- 创建映射
- Promise.all()接收由期约组成的可迭代对象
- Promise.race()接收由期约组成的可迭代对象
- yield*操作符,在生成器中使用
迭代器协议
// 可迭代对象
let arr = [1,2]
// 迭代器工厂函数
console.log(arr[Symbol.iterator]); // ƒ values() { [native code] }
// 迭代器
let iter = arr[Symbol.iterator]()
console.log(iter); // Array Iterator {}
// 执行迭代
console.log(iter.next()); // {value: 1, done: false}
console.log(iter.next()); // {value: 2, done: false}
console.log(iter.next()); // {value: undefined, done: true}
console.log(iter.next()); // {value: undefined, done: true}
console.log(iter.next()); // {value: undefined, done: true}
每次成功调用 next(),都会返回一个 IteratorResult 对象,包含两个属性:done 和 value。done 是一个布尔值,done: true 状态称为“耗尽”,此时此时继续调用next()会返回{value: undefined, done: true}。value 包含可迭代对象的下一个值(done 为 false),或者 undefined(done 为 true)。
每个迭代器都表示对可迭代对象的一次性有序遍历。不同迭代器的实例相互之间没有联系,只会独 立地遍历可迭代对象:
let arr = ['foo', 'bar'];
let iter1 = arr[Symbol.iterator]();
let iter2 = arr[Symbol.iterator]();
console.log(iter1.next()); // { done: false, value: 'foo' }
console.log(iter2.next()); // { done: false, value: 'foo' }
console.log(iter2.next()); // { done: false, value: 'bar' }
console.log(iter1.next()); // { done: false, value: 'bar' }
迭代器并不与可迭代对象某个时刻的快照绑定,而仅仅是使用游标来记录遍历可迭代对象的历程。