概念

迭代器模式(Iterator Pattern)是一种行为型设计模式,它提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示


迭代器模式通常涉及两个核心角色:迭代器(Iterator)和可迭代对象(Iterable)

  • 迭代器(Iterator)

    负责定义访问和遍历聚合对象元素的接口

    迭代器对象通常会追踪当前位置,并提供方法来获取下一个元素、检查是否还有下一个元素等

  • 可迭代对象(Iterable)

    表示包含一组元素的聚合对象,并提供一个方法来获取对应的迭代器

    可迭代对象可以是集合类、数组、列表等


迭代器模式的核心思想是将遍历聚合对象的行为抽象出来,并将其封装到迭代器对象中,从而实现聚合对象与遍历算法的解耦

这种模式的优点在于,可以统一遍历接口,使得客户端代码与聚合对象的内部结构解耦,同时也提供了一种通用的遍历方法,适用于各种不同类型的聚合对象

迭代器模式适用于以下情况:

  • 当需要对聚合对象进行遍历,并且希望遍历算法与聚合对象的内部结构解耦时,可以使用迭代器模式
  • 当希望提供一种统一的遍历接口,使得客户端代码可以统一处理不同类型的聚合对象时,迭代器模式也是一个很好的选择


举个简单的例子,考虑一个集合类(如列表、数组)中包含一组元素

而迭代器模式可以将遍历集合元素的算法抽象成一个迭代器对象,从而使得客户端代码可以通过迭代器对象来访问和遍历集合元素,而不需要了解集合对象的内部结构


实现条件

  1. 存在一个聚合对象

    迭代器模式适用于需要遍历一组元素的情况,因此需要存在一个聚合对象来存储这组元素

  2. 需要对元素进行遍历操作

    迭代器模式适用于需要对聚合对象中的元素进行遍历操作的情况

  3. 需要对遍历方式进行抽象

    迭代器模式将遍历方式抽象成迭代器对象,并提供一组统一的接口来对元素进行遍历,从而使得客户端可以统一地处理不同类型的聚合对象

  4. 需要支持多种遍历方式

    迭代器模式允许定义多种不同的迭代器对象,从而支持多种不同的遍历方式,例如正向遍历、逆向遍历、按照某种顺序遍历等


优点

  1. 简化聚合对象的接口

    迭代器模式将遍历操作封装在迭代器对象中,使得客户端可以统一地对聚合对象进行遍历操作,简化了聚合对象的接口

  2. 解耦迭代算法和聚合对象

    迭代器模式将遍历算法与聚合对象进行了解耦,使得聚合对象的内部结构可以独立于其遍历算法的变化

  3. 支持多种遍历方式

    迭代器模式允许定义多种不同的迭代器对象,从而支持多种不同的遍历方式,例如正向遍历、逆向遍历、按照某种顺序遍历等

  4. 增加代码复用性

    迭代器模式将遍历操作封装在迭代器对象中,可以在不同的聚合对象之间重复使用相同的迭代器对象,从而提高了代码的复用性

  5. 简化客户端代码

    迭代器模式使得客户端可以统一地对聚合对象进行遍历操作,不需要关心聚合对象的内部结构和遍历方式,从而简化了客户端的代码


缺点

  1. 增加对象数量

    迭代器模式需要定义迭代器对象和聚合对象之间的关系,可能会增加对象数量,从而增加了系统的复杂度

  2. 可能降低性能

    迭代器模式需要在迭代器对象中保存迭代状态,并且可能需要频繁地进行状态转换,可能会降低系统的性能

  3. 可能增加代码复杂度

    迭代器模式需要定义迭代器对象和聚合对象之间的关系,并且可能需要在迭代器对象中保存迭代状态,可能会增加代码的复杂度


实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* ListCollection类定义了一个列表集合,支持添加元素、删除元素、迭代遍历以及对函数执行或其它类型数据打印的功能。
*/
class ListCollection {
/**
* 构造函数初始化集合元素数组。
*/
constructor() {
this.elements = [] // 初始化集合元素数组
}

/**
* 向集合中添加元素。
* @param {any} element 要添加到集合中的元素。
*/
add(element) {
this.elements.push(element)
}

/**
* 从集合中删除指定索引的元素。
* @param {number} index 要删除元素的索引。
* @throws {Error} 如果索引超出范围,则抛出错误。
*/
remove(index) {
if (index >= 0 && index < this.elements.length) {
this.elements.splice(index, 1)
} else {
throw new Error('Index out of bounds')
}
}

/**
* 实现迭代器协议,允许集合被遍历。
* @yields {any} 遍历集合中的每个元素。
*/
*[Symbol.iterator]() {
for (let element of this.elements) {
yield element
}
}

/**
* 遍历集合,如果元素是函数则执行,否则打印元素。
* 该方法为内部方法,不直接暴露给外部使用。
*/
forEachExecuteOrLog() {
for (let item of this) {
if (typeof item === 'function') {
item() // 执行函数
} else {
console.log(item) // 输出其他类型数据
}
}
}

}

export default new ListCollection


怎么使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import ListCollection from '../IteratorPattern'

/**
* 向列表中添加各种类型的元素。
* 包括数字、函数、对象和字符串。
*/
ListCollection.add(1);
ListCollection.add(function() { console.log('Function executed!') })
ListCollection.add({ key: 'value' })
ListCollection.add('Text')

/**
* 遍历列表中的每个元素,并尝试执行它们。
* 如果元素是函数,则执行该函数;
* 如果元素是字符串或其他类型,则仅在控制台打印。
*/
ListCollection.forEachExecuteOrLog()


场景

  1. 数组的迭代方法

    JavaScript 的原生数组方法,如 forEach, map, filter, reduce 等,都是迭代器模式的体现

    这些方法让你能够遍历数组并对每个元素执行操作,而无需直接操作数组的索引

  2. DOM 遍历

    在处理DOM元素时,经常需要遍历节点树

    虽然DOM API本身不是严格意义上的迭代器模式实现,但你可以使用类似迭代器的思维模式,比如使用 NodeList.prototype.forEach 或者创建自己的迭代器来遍历DOM子节点

  3. React中的Keys

    在React中,当渲染列表时,为列表项分配唯一key是一个最佳实践

    虽然这不是直接的迭代器模式,但它体现了对集合元素进行迭代和管理的思想,确保高效更新虚拟DOM

  4. Redux Saga / RxJS

    在状态管理库Redux中,Redux Saga使用迭代器函数来处理异步操作流,这是一种高级的迭代器模式应用

    同样,RxJS(Reactive Extensions for JavaScript)利用Observables来处理异步数据流,其背后的概念与迭代器模式紧密相关,提供了强大的数据处理能力

  5. Iterable Protocols

    ECMAScript中的可迭代协议(例如使用 [Symbol.iterator] 方法)允许对象定义自身的迭代行为

    这使得任何实现了这个协议的对象都可以被 for…of 循环遍历,这是迭代器模式的直接应用

  6. Immutable.js

    这是一个流行的JavaScript库,用于不可变数据结构的处理

    它内部广泛使用迭代器模式来遍历Map、List等数据结构,同时保持数据不变性

  7. Lodash/Underscore

    这些实用库提供了丰富的集合操作方法,如 .each, .map, _.filter 等,它们背后都运用了迭代器模式的理念,简化了对集合数据的操作

  8. Vue.js的v-for指令

    Vue框架中的v-for指令用来遍历数组或对象属性,并渲染每个项目到DOM

    虽然这是模板语法的一部分,但它基于迭代器模式思想,让用户能以声明式的方式遍历数据


源代码