设计模式

wiki中将设计模式分为四类,别离是:

  • 创立模式(creational patterns)
  • 构造模式(structural patterns)
  • 行为模式(behavioral patterns)
  • 并发模式(concurrency patterns)

迭代器模式属于其中的行为模式。

迭代器做的只有一件事,就是解决汇合的遍历问题。

以下是wiki对迭代器模式的概括性形容:

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

提供一种在不裸露底层示意的状况下按程序拜访聚合对象元素的形式。

这个形容乍一看有点形象,但其实咱们罕用的for循环就能够看作是迭代器模式的利用,在遍历时并不知道数组元素的具体示意:

let array = [1, 2, 3, 4, 5, 6];for (let i = 0; i < array.length; i ++) {  console.log(array[i]);}

如果不利用迭代器,咱们想要遍历array数组就得像上面这么做:

console.log(array[0]);console.log(array[1]);console.log(array[2]);// ...

但应用for循环的前提是array是数组,这样咱们能力应用索引去遍历这个汇合,所以下面这种for循环就是针对数组类型汇合的一种迭代器实现。

定义

上面再进一步理解迭代器模式是什么:

In object-oriented programming, the iterator pattern is a design pattern) in which an iterator is used to traverse a container) and access the container's elements. The iterator pattern decouples algorithms from containers; in some cases, algorithms are necessarily container-specific and thus cannot be decoupled.

For example, the hypothetical algorithm SearchForElement can be implemented generally using a specified type of iterator rather than implementing it as a container-specific algorithm. This allows SearchForElement to be used on any container that supports the required type of iterator.

在面向对象编程中,迭代器模式是一种设计模式,应用迭代器来遍历容器并拜访容器中的元素。迭代器模式使算法与容器拆散;在某些状况下,算法是特定于容器的,因而无奈拆散。

(能够看出,下面的for循环就是特定于容器,也就是数组类型的容器,同理,数组的原型办法forEach也是如此)

例如,假如算法'SearchForElement'能够应用特定类型的迭代器实现,而不是作为特定于容器的算法来实现。这样就能够在任何反对所需迭代器类型的容器上应用'SearchForElement'。

解决的问题

从上述定义中咱们就能够晓得,迭代器模式就是用于解决如何去遍历容器中的元素。

上面是wiki给出的形容:

  • The elements of an aggregate object should be accessed and traversed without exposing its representation (data structures).
  • New traversal operations should be defined for an aggregate object without changing its interface.

Defining access and traversal operations in the aggregate interface is inflexible because it commits the aggregate to particular access and traversal operations and makes it impossible to add new operations later without having to change the aggregate interface.

翻译过去即:

  • 拜访和遍历聚合对象中的元素时,不应裸露其示意模式(数据结构)
  • 应在不扭转聚合对象接口的状况下,为聚合对象定义新的遍历操作

在聚合接口中定义拜访和遍历操作是不灵便的,因为这样做会使聚合对象遵从于特定的拜访和遍历操作,当前就不可能在不扭转聚合接口的状况下增加新的操作。

(这段话的意思应该是指,拜访和遍历操作应该由聚合对象自行实现)

如何做

那具体应该如何操作呢?

  • Define a separate (iterator) object that encapsulates accessing and traversing an aggregate object.
  • Clients use an iterator to access and traverse an aggregate without knowing its representation (data structures).

Different iterators can be used to access and traverse an aggregate in different ways.
New access and traversal operations can be defined independently by defining new iterators.

翻译过去即:

  • 定义一个独自的(迭代器)对象,封装对聚合对象的拜访和遍历
  • 客户端应用迭代器来拜访和遍历聚合对象,而无需理解其示意模式(数据结构)

不同的迭代器能够不同的形式拜访和遍历聚合对象。

通过定义新的迭代器,能够独立定义新的拜访和遍历操作。

ES6实现

在ES6之前,能够被遍历的对象严格来说只有数组,所以遍历办法都是针对数组这个汇合类型;甚至类数组都不能间接调用forEach办法。

但当初E6中新增了Map和Set等汇合类型,原来的遍历办法就更大地暴露出了它们的缺点:

  • 遍历的对象必须是数组
  • 应用的是递增索引的形式

[Symbol.iterator]()

为此ES6定义了新的可迭代协定,即任何对象都能够通过定义一个迭代器接口,来生成迭代器,这个接口被称为“迭代器工厂”,即[Symbol.iterator](),来定义其被迭代的形式;此时这个类型便被称为“可迭代对象”(Iterable)。

迭代器能够提供给各种遍历办法调用,比方:for-of、数组解构、扩大操作符等操作,都会调用可迭代对象的迭代器接口失去其对应的迭代器,而后调用迭代器的next()办法来实现对可迭代对象的遍历,如果有提前结束遍历的需要,还能够给迭代器定义return()办法。

next()return()办法返回的对象被称为“IteratorResult对象”,蕴含有value和done两个属性,value示意迭代值,done示意遍历是否完结,当done的值为true时,代表遍历完结。

以下是一个简略的例子:

class Counter {    constructor(limit) {        this.limit = limit;    }   // 迭代器接口    [Symbol.iterator]() {        let count = 1,          o = this;        return { // 迭代器本体          next() {              if( count <= o.limit) {                return { // IteratorResult对象                   done: false,                   value: count++                };              } else {                return {                   done: true                 };               }           },           return() {               console.log( 'Exiting early' );               return {                  done: true                };            }        }   }}let counter1 = new Counter(5);for (let i of counter1) {    if(i > 2) {        break;    }    console.log( i );}// 1// 2// Exiting early

在上述代码中,for-of在遍历过程中相当于调用迭代器的next()办法,当执行到break;语句时,就相当于调用了迭代器的return()办法;上述代码等价于:

// 通过调用迭代器工厂,获取迭代器对象const iterator = counter1[Symbol.iterator]();let iterator_result = { done: false };while(!iterator_result.done) {   iterator_result = iterator.next();   if (iterator_result.value > 2) {     iterator_result = iterator.return();   } else {     console.log(iterator_result.value);   }}

在ES6中,Array、Map和Set等汇合类型都内置了独自的迭代器。

每个内置可迭代对象的默认迭代器接口所产生的迭代器,也有本人的默认迭代器函数,返回指向本身(并未严格实现Iterable接口,默认迭代器函数不能创立新的迭代器),即如下所示:

[Symbol.iterator]() {    // ...    return {        next() {           // ...         },        return() {            // ...        },       [Symbol.iterator]() {         return this;       }    }}

Generator

另外,ES6还提供了生成迭代器的函数,简称“生成器(Generator)”,相当于迭代器工厂[Symbol.iterator]()这个接口,能够通过*来申明生成器函数,并配合yield来实现更灵便的遍历。

来看上面的例子:

function* generatorFn3() {  // ...一些操作  yield 'foo';  // ...一些操作  yield 'bar';  // ...一些操作  return 'baz';}let g3 = generatorFn3(); // 生成一个迭代器

此时通过迭代器来遍历,当执行第n次g3.next(),就会执行第n个yield(没有yield就是return)之前的代码,返回对象中的value即对应yield或return跟着的值,当执行到yield时,done为false,当执行到return,done就为true,如下所示:

console.log( g3.next() ); // { value: 'foo', done: false }console.log( g3.next() ); // { value: 'bar', done: false }console.log( g3.next() ); // { value: 'baz', done: true }

以上代码相当于:

g3 = generatorFn3();for(const x of g3) {    console.log( x );}

但只会输入foo和bar,return前面跟着的值不属于迭代值。

总结

个别状况下,咱们不须要本人实现迭代器,ES6内置的迭代器就足够日常的开发应用了,但理解迭代器模式,以及ES6提供的迭代器工厂和生成器函数,能够帮忙咱们解决非凡的开发需要,设计更灵便的代码计划(利用generator和yield)。