迭代(iteration)指的是按照顺序反复执行一段程序的操作,通常会有明确的终止条件。
循环是迭代的基础。循环可以指定迭代的次数和每次迭代执行的操作。
迭代的对象通常是一个有序集合。有序集合,即可以按某种顺序对集合的元素进行遍历的集合。
在开发实践中发现,通过某种特定的数据结构(如数组)来进行迭代,有两个不足:
- 迭代的方式不统一,迭代前需要知道如何使用相应的数据结构。
- 迭代的遍历顺序不确定,不同的数据结构往往会有不同的遍历顺序。
在 ES6 之前,执行迭代必须使用循环或其它辅助结构。随着代码量的增加,代码会变得混乱。
很多语言都通过原生语言结构解决了这个问题,开发者无须事先知道具体的迭代方式就能实现迭代。
这个解决方案就是迭代器模式。Python、Java、C++ 等语言都对迭代器模式提供了完备的支持。
从 ES6 开始 ECMAScript 也支持了迭代模式。
主要参考资料:
- 《JavaScript 高级程序设计(第4版)》- P183(208/931)
迭代器模式
迭代器模式描述了一个方案:实现接口 Iterable 的可迭代对象(iterable)可以通过实现接口 Iterator 的迭代器(iterator)消费(consume)。
接口 Iterable
接口 Iterable 即可迭代协议,实现接口 Iterable 的结构被称为可迭代对象(Iterable)。
可迭代对象中拥有可以被遍历的元素。
可迭代对象包含的元素是有限的,而且具有确定的遍历顺序。
可迭代对象(实现接口 Iterable )的要求:
- 具有支持迭代的自我识别能力
- 可以创建实现接口 Iterator 的对象,即可以创建迭代器
接口 Iterator
接口 Iterator 即迭代器协议,实现接口 Iterator 的结构被称为迭代器(Iterator)。
迭代器从属于可迭代对象,是由可迭代对象创建的,是一次性使用的对象。
迭代器用于迭代与其关联的可迭代对象中的元素,任何可迭代对象都可以被迭代器消费(迭代)。
迭代器要求拥有以下方法:
- next() ,对可迭代对象中的元素进行有序遍历,获取可迭代对象中的下一个元素的值。
返回值:对象 IteratorResult(迭代器结果) - retrun() (可选),当迭代器提前关闭时被调用。
返回值:对象 IteratorResult(迭代器结果)
对象 IteratorResult 被称为迭代器结果,是由迭代器的方法 next() 返回的。
迭代器结果拥有两个属性:
- done ,布尔值,表示可迭代对象是否已经被完整遍历。
- value ,可迭代对象中的元素的值。
ES6 新增两个高级特性:迭代器、生成器,来支持迭代器模式。
迭代器
迭代器是从属于某个可迭代对象的,是由可迭代对象创建的。所以严格来说,迭代器指的是某个可迭代对象的迭代器。
在 ECMAScript 中实现可迭代对象:
- 使用一个键为符号 Symbol.iterator 的方法作为默认迭代器(函数)。
- 符号方法 Symbol.iterator 通常为一个迭代器工厂函数,返回一个迭代器。
ECMAScript 中的迭代器结果:
IteratorResult: { done: boolean, value: any }
value 的默认值为 undefined 。
当迭代器结果中的 done 为 true 时,表示迭代器对可迭代对象进行了完整的遍历。
此时可迭代对象没有下一个元素,所以此时迭代器结果中的 value 通常为 undefined 。
示例:
- 迭代器的创建与使用:
// 定义可迭代对象的类 class Iterable { constructor(limit) { this.limit = limit } // 定义默认迭代器函数,用于创建迭代器 [Symbol.iterator]() { let count = 1, limit = this.limit return { next() { if(count <= limit) { return { done: false, value: count++ } // 迭代器结果 } else { return { done: true, value: undefined } } } } // 迭代器 } } // 创建可迭代对象 let iterable = new Iterable(2) // 创建迭代器 let iter = iterable[Symbol.iterator]() // 使用迭代器 console.log(iter.next()) // { done: false, value: 1 } console.log(iter.next()) // { done: true, value: 2 } console.log(iter.next()) // { done: false, value: undefined }
ECMAScript 的很多内置类型都实现了接口 Iterable :
- String
- Array
- Map
- Set
- 对象 argument
- NodeList 等 DOM 集合类型
示例:
- 创建并使用 Array 实例对象的迭代器
// 创建 Array 实例对象 let arr = [1, 2, 3] // 创建迭代器 let iter = arr[Symbol.iterator]() // 使用迭代器 console.log(iter.next()) // { done: false, value: 1 } console.log(iter.next()) // { done: true, value: 2 } console.log(iter.next()) // { done: true, value: 3 }
如果某个对象的原型链上的父类实现了接口 Iterable ,那么该对象也实现了 Iterable 。
自动迭代
ECMAScript 中有一些原生语法会自动兼容对可迭代对象的迭代。
这些原生语法会自动对可迭代对象进行一次完整迭代,主要过程为:
- 创建可迭代对象的迭代器。
- 调用迭代器的方法 next() 。
- 判断迭代器结果的属性 done 的值。
- 如果迭代器结果的属性 done 为 false ,则获取迭代器结果的属性 value 的值。并转到过程 2 。
- 如果迭代器结果的属性 done 为 true ,则忽视迭代器结果的属性 value 。
- 结束。
自动兼容可迭代对象的原生语法:
- 语句 for-of
- 数组解构
- 扩展操作符
- Array.from
- 创建 Set 实例
- 创建 Map 实例
- Promise.all() 接收由期约组成的可迭代对象
- Promise.race() 接收由期约组成的可迭代对象
- 在生成器中使用操作符 yield*
示例:
class Iterable {
constructor(limit) {
this.limit = limit
}
[Symbol.iterator]() {
let count = 1,
limit = this.limit
return {
next() {
if(count <= limit) {
return { done: false, value: count++ }
} else {
return { done: true, value: undefined }
}
}
}
}
}
let iterable = new Iterable(2)
// 自动迭代
for(const item of iterable) {
console.log(item)
}
// 输出:
// 1
// 2
提前关闭迭代器
提前关闭迭代器是指在完整迭代可迭代对象之前关闭迭代器。
通过是否实现方法 return() 来决定迭代器是否是可提前关闭的。比如,类型 Array 没有实现方法 return() ,是不可提前关闭的。
提前关闭迭代器会自动调用迭代器中的方法 return() 。
方法 return() 必须返回一个迭代器结果对象,可以只返回 { done: true } 。
提前关闭迭代器的情况:
- 在循环 for-of 中,通过 break、continue、throw 提前退出循环。
- 解构操作并未消费所有值。
示例:
// 迭代器类
class Iterator {
constructor(count, limit) {
this.count = count
this.limit = limit
}
next() {
if(this.count <= this.limit) {
return { done: false, value: this.count++ }
} else {
return { done: true, value: undefined }
}
}
// 定义方法 return
return() {
console.log('Exiting early')
return { done: true }
}
}
// 可迭代对象类
class Iterable {
constructor(limit) {
this.limit = limit
}
[Symbol.iterator]() {
let count = 1,
limit = this.limit
return new Iterator(count, limit)
}
}
let iterable = new Iterable(2)
for(const item of iterable) {
if(item > 1){
break; // 提前结束迭代
}
console.log(item)
}
// 输出:
// 1
// Exiting early
使迭代器成为可迭代对象
一些可迭代对象会使其迭代器成为可迭代对象,并使迭代器的默认迭代器函数返回迭代器自身。(比如,类型 Array ):
// 迭代器类
class Iterator {
constructor(count, limit) {
this.count = count
this.limit = limit
}
next() {
if(this.count <= this.limit) {
return { done: false, value: this.count++ }
} else {
return { done: true, value: undefined }
}
}
// 定义默认迭代器函数
[Symbol.iterator]() {
return this
}
}
// 可迭代对象类
class Iterable {
constructor(limit) {
this.limit = limit
}
[Symbol.iterator]() {
let count = 1,
limit = this.limit
return new Iterator(count, limit)
}
}
// 创建可迭代对象
let iterable = new Iterable(2)
// 创建迭代器
let iter = iterable[Symbol.iterator]()
// 迭代可迭代对象
for(const item of iterable) {
console.log(item)
}
// 输出:
// 1
// 2
// 迭代迭代器
for(const item of iter) {
console.log(item)
}
// 输出:
// 1
// 2