设计模式4-迭代器模式

迭代器模式

定义

迭代器模式是指提供一种方法顺序访问一个集合对象中的各个元素,而不需要暴露该对象的内部表示。

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

主要解决

不需要关心对象内部表示来遍历整个对象。

内部迭代器

内部迭代器是指事先定义好迭代器的迭代规则,他完全接受整个迭代过程,外部只需要一次初始调用。

实现

我们实现一个each内部迭代器,接受两个参数:

  • arr:要被迭代的数组
  • fn:迭代规则函数

fn在每一次数组循环时都会执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const each = function(arr, fn){
for(let i = 0, len = arr.length; i < len; i++){
fn(i, arr[i])
}
}

//调用
each([1,2,3], function(index, item){
console.log(index, item)
})


/*
*结果
*0 1
*1 2
*2 3
*/

内部迭代器的缺点就是无法很好的组合多个迭代,只有在一个迭代规则中嵌入另外一个迭代器,这样未必显得代码冗余,影响代码可读性。所以产生了外部迭代器。

外部迭代器

外部迭代器一般会提供一个next类似的函数,每调用一次,就会返回该次迭代的结果。我们可以在外部拿到结果,进行更多的操作。

下面我们简单实现一个外部迭代器的原型:

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
const Iterator = function(obj){
let current = 0
let maxLen = obj.length
let next = function(){
if(current < maxLen){
current++
return {
value: obj[current],
done: false
}
}else{
return {
value: undefined,
done: true
}
}
}
return {
next,
len: maxLen
}
}

let arr = [1,2,3]

let iterator_arr = Iterator(arr)

iterator_arr.next()
/*
*{
* value: 1,
* done: false
*}
*/

上面的函数实现了ES6提供的Iterator接口最基本的部分。我们可以在外部拿到迭代结果,现在我们可以同时拿到两个迭代器的结果而不用嵌套迭代器。

见到那写一下两个迭代器元素的比较:

1
2
3
4
5
6
7
8
9
let compare = function(iterator1, iterator2){
//比较元素长度
if(iterator1.length !== iterator2.length){
return false
}
while(!iterator1.next().done && !iterator2.next().done && iterator1.next().value !== iterator2.next().value){
return false
}
}

JavaScript中遍历

迭代器说到底还是对某种数据结构进行遍历的的一个接口。在一般的高级语言中,对于特殊的数据结构,比如数组都会封装一个方法进行遍历。而在JavaScript中,表示集合类的数据结构中,包括ES6增加的MapSet,一共就有4种:

  • Object
  • Array
  • Set
  • Map

对于数组和对象,我在JavaScript中数组与对象的遍历方法中,进行了详细的探讨。

在那篇文章中,实际上都是数组或对象的内部迭代器的实例,比如的数组的forEach等方法。

而在ES6中,提供了原生的Iterator接口来实现外部迭代器,而在Java等语言中,早就提供了该接口。

Iterator

Iterator

Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环(详见下文)。当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。

一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。

ES6 规定,

  • 默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。
  • Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。
  • 属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内。

如:

1
2
3
4
5
6
7
8
9
10
11
12
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};

上面代码中,对象obj是可遍历的(iterable),因为具有Symbol.iterator属性。执行这个属性,会返回一个遍历器对象。该对象的根本特征就是具有next方法。每次调用next方法,都会返回一个代表当前成员的信息对象,具有valuedone两个属性。

ES6中有一些对象已经原生实现了Iterator 接口:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

值得注意的是Object并没有原生实现iterator,原因是:

对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作 Map 结构使用,ES5 没有 Map 结构,而 ES6 原生提供了。

当我们对一个数组完成赋值,其Symbol.iterator属性就随之生成了。比如下面的例子:

1
2
3
4
5
6
7
let arr = ['a', 'b', 'c']
let ite = arr[Symbol.iterator]()

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

调用 Iterator 接口的场合

有一些场合会默认调用 Iterator 接口(即Symbol.iterator方法),除了下文会介绍的for...of循环,还有几个别的场合。

(1)解构赋值

对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。

1
2
3
4
5
6
7
let set = new Set().add('a').add('b').add('c');

let [x,y] = set;
// x='a'; y='b'

let [first, ...rest] = set;
// first='a'; rest=['b','c'];

(2)扩展运算符

扩展运算符(…)也会调用默认的 Iterator 接口。

1
2
3
4
5
6
7
8
// 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']

// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']

实际上,这提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,转为数组。也就是说,只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。

1
let arr = [...iterable];

(3)yield*

yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};

var iterator = generator();

iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }

(4)其他场合

由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。

  • for…of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]])
  • Promise.all()
  • Promise.race()

Iterator 接口与 Generator 函数

Symbol.iterator()方法的最简单实现,还是使用Generator 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let myIterable = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
};
[...myIterable] // [1, 2, 3]

// 或者采用下面的简洁写法

let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};

for (let x of obj) {
console.log(x);
}
// "hello"
// "world"

上面代码中,Symbol.iterator()方法几乎不用部署任何代码,只要用 yield 命令给出每一步的返回值即可。

for…of 循环

ES6 借鉴 C++、Java、C# 和 Python 语言,引入了for...of循环,作为遍历所有数据结构的统一的方法。

一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。

for...of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。

示例

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
//数组
const arr = ['red', 'green', 'blue'];

for(let v of arr) {
console.log(v); // red green blue
}


//Set
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
console.log(e);
}
// Gecko
// Trident
// Webkit

//Map
var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262

计算生成的数据结构

有些数据结构是在现有数据结构的基础上,计算生成的。比如,ES6 的数组、Set、Map 都部署了以下三个方法,调用后都返回遍历器对象。

  • entries() 返回一个遍历器对象,用来遍历[键名, 键值]组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用entries方法。
  • keys() 返回一个遍历器对象,用来遍历所有的键名。
  • values() 返回一个遍历器对象,用来遍历所有的键值。

参考

Powered by Hexo and Hexo-theme-hiker

Copyright © 2019 - 2024 My Wonderland All Rights Reserved.

UV : | PV :