共计 4670 个字符,预计需要花费 12 分钟才能阅读完成。
设计模式
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)。