Iterator(迭代器模式)属于行为型模式。
意图:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
这种设计模式要解决的根本问题是,聚合的种类有很多,比如对象、链表、数组、甚至自定义结构,但遍历这些结构时,不同结构的遍历方式又不同,所以我们必须了解每种结构的内部定义才能遍历。
比如数组我们可以利用 length + for 循环,对象我们可以 Object.keys,链表比较麻烦,需要内部暴露出元素的 next
以操作指向下一个元素。
迭代器模式可以做到用同一种 API 遍历任意类型聚合对象,且不用关心聚合对象的内部结构。
这种模式和 Array.from
有点像,但其实真正的迭代器在 JS 里是 obj[Symbol.iterator]()
,也就是一个对象实现了 [Symbol.interator]
,就认为是可遍历的。
如果看不懂上面的意图介绍,没有关系,设计模式需要在日常工作里用起来,结合例子可以加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。
迭代器的例子非常简单,我们平时工作中有大量使用到。
generator
天生为迭代器的 API:
function* func () { yield 'a'; yield 'b'; return 'c'; } var run = func(); run.next() // {value: "a", done: false} run.next() // {value: "b", done: false} run.next() // {value: "c", done: true}
我们无需关心 generator 内部是何种存储结构,只需要调用 .next()
,并根据返回的 done
来判断是否遍历完即可。在 generator 的场景中,迭代器不仅用来遍历聚合,还用于执行代码。
我们可以用迭代器的方式遍历数组:
const arr = [1, 2, 3] const run = arr[Symbol.iterator]() run.next() // {value: 1, done: false} run.next() // {value: 2, done: false} run.next() // {value: 2, done: false} run.next() // {value: undefined, done: true}
可能有人觉得这是画蛇添足,因为毕竟遍历数组用 for 循环更方便,但这就是设计模式与非设计模式思维的区别,重要的不是用熟悉简单的 API 快速满足需求,设计模式关注的是如何统一、抽象、低耦合的编码。
Map 对象也可以用迭代器方式遍历:
const citys = new Map([['北京', 1], ['上海', 2], ['杭州', 3]]) const run = citys.entries() run.next() // {value: ['北京', 1], done: false} run.next() // {value: ['上海', 2], done: false} run.next() // {value: ['杭州', 3], done: false} run.next() // {value: undefined, done: true}
从上面的例子可以看出,虽然用迭代器遍历数组看上去比 for 循环麻烦一点,但当我们把所有聚合类型放到一起看时,可以发现只有迭代器的 API 是最统一的,是唯一一个不需要关心聚合类型就可以完成遍历的方案。
意图:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
再来看意图,就非常好理解了,我们无需关心 数组、generator、Map 内部是如何存储的,就可以进行遍历。实际上,深究 generator 内部的存储结构也没有意义,如果我们不用迭代器进行遍历,那么对于复杂结构的遍历成本是非常高的。
Aggregate
: 聚合,需要定义创建迭代器的接口。比如前端规范的 [Symbol.iterator]()
,或者这里定义的 CreateIterator()
。Iterator
: 迭代器,定义了访问与遍历的 API。迭代器的定义很简单,实现时要考虑的因素可不少,包括:
next()
控制迭代,还是由外层统一控制迭代。下面例子使用 typescript 编写。
// 定义聚合接口 interface Aggregate{ getIterator: () => Iterator } // 定义迭代器接口 interface Iterator { // 指向下一个 next: () => void } // 定义一个聚合 class List implements Aggregate { // 存储元素 public values: string[] // 游标 public index: number getIterator() { return new ConcreteIterator(this); } } // List 的迭代器 class ConcreteIterator implements Iterator { constructor(list: List) { this.list = list } next() { return this.list.values[this.list.index] // 注意边界情况,这里就不展开 this.list.index++ } }
如果你只是遍历数组,直接用 for 循环会比迭代器方便很多,没必要为了用设计模式而用设计模式。迭代器仅在以下情况可以考虑用于数组:
迭代器模式比较好理解,这里补充几个相关设计模式:
讨论地址是:精读《设计模式 - Iterator 迭代器模式》· Issue #298 · dt-fe/weekly
如果你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。
关注 前端精读微信公众号
版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证)