乐趣区

关于javascript:精读设计模式-Iterator-迭代器模式

Iterator(迭代器模式)

Iterator(迭代器模式)属于行为型模式。

用意:提供一种办法程序拜访一个聚合对象中的各个元素,而又不须要裸露该对象的外部示意。

这种设计模式要解决的基本问题是,聚合的品种有很多,比方对象、链表、数组、甚至自定义构造,但遍历这些构造时,不同构造的遍历形式又不同,所以咱们必须理解每种构造的外部定义能力遍历。

比方数组咱们能够利用 length + for 循环,对象咱们能够 Object.keys,链表比拟麻烦,须要外部暴露出元素的 next 以操作指向下一个元素。

迭代器模式能够做到用同一种 API 遍历任意类型聚合对象,且不必关怀聚合对象的内部结构。

这种模式和 Array.from 有点像,但其实真正的迭代器在 JS 里是 obj[Symbol.iterator](),也就是一个对象实现了 [Symbol.interator],就认为是可遍历的。

举例子

如果看不懂下面的用意介绍,没有关系,设计模式须要在日常工作里用起来,联合例子能够加深你的了解,上面我筹备了三个例子,让你领会什么场景下会用到这种设计模式。

迭代器的例子非常简单,咱们平时工作中有大量应用到。

generator

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 迭代器

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。

迭代器的定义很简略,实现时要思考的因素可不少,包含:

  • 健壮性。即迭代过程中减少、删除元素后,还能失常遍历。或者遍历空聚合时也要能失常工作。
  • 内部管制迭代还是外部。即相似 KOA 由插件调用 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 循环会比迭代器不便很多,没必要为了用设计模式而用设计模式。迭代器仅在以下状况能够思考用于数组:

  1. 这个数组比拟非凡,是 N 维数组,须要一次性遍历完,那么能够用迭代器。
  2. 同时遍历数组和其余类型的聚合,则不管数组还是其余聚合,都用雷同的迭代器模式遍历最好。

总结

迭代器模式比拟好了解,这里补充几个相干设计模式:

  • 迭代器能够和组合模式配合,在组合构造内进行递归,这样一个迭代器就能够遍历完所有组合。
  • 能够用工厂模式 + 多态模式,实例化不同的迭代器的实例。
  • 迭代器模式还能够与备忘录模式配合应用,当咱们要还原迭代器状态时,适宜在迭代器外部应用备忘录模式进行状态存储。

探讨地址是:精读《设计模式 – Iterator 迭代器模式》· Issue #298 · dt-fe/weekly

如果你想参加探讨,请 点击这里,每周都有新的主题,周末或周一公布。前端精读 – 帮你筛选靠谱的内容。

关注 前端精读微信公众号

版权申明:自在转载 - 非商用 - 非衍生 - 放弃署名(创意共享 3.0 许可证)

退出移动版