一、概念
遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6有一种新的遍历方式,for...of,而Iterator的主要作用,就是支持此操作。
二、是否具备遍历器(Iterator)接口
在ES6中,有些数据结构原生具备Iterator接口(比如数组),即不用任何处理,就可以被for...of循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了Symbol.iterator属性(详见下文),另外一些数据结构没有。
凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。它们三者不需要我们手动部署Symbol.iterator属性就可以使用for...of遍历功能。而普通对象使用for...of遍历就会报错,因为它没有部署该接口。
三、部署接口
如果你需要使原本不具备for...of遍历功能的集合具备该功能,就需要手动为它部署。ES6有许多内置的Symbol值,这些就是接口。
下面我们利用迭代器中的原理来给对象生成一个迭代器,实现让对象可以使用for...of
var person={
name: 'zz',
age: 18
}
//给person对象添加一个iterator接口
person[Symbol.iterator] = function(){
//使用object.keys()方法把j对象中的k值读取出来存在数组当中
var arr = Object.keys(person);
var i = 0;
return {
//ES6中next()迭代方法,自动迭代
next(){
if(i < arr.length){
//如果done为false,继续迭代
return {
//返回迭代的最后结果,如果是一个对象,那么for of的时候需要用解构
value: {
k : arr[i],
val: person[arr[i++]]
},
done: false
}
}else{
//如果done为true,继续迭代
return {
value: null,
done : true
}
}
}
}
}
//解构获取返回得到的对象,输出k值,val值
for(var {key,val} of person){
console.log(key,val);
}
我们可以看到:
1、通过为person对象部署Symbol.iterator接口,就使它实现了for...of 功能。
2、Symbol.iterator中返回一个对象,该对象包含一个next() 方法,定义了遍历功能。
3、next方法中的value和done,value是遍历过程中返回的键值对信息,done是一个表示遍历是否结束的布尔值。
补充:
1、遍历器对象除了具有next方法,还可以具有return方法和throw方法。如果你自己写遍历器对象生成函数,那么next方法是必须部署的,return方法和throw方法是否部署是可选的。
当一个解构在遍历的时候异常提前退出(比如break,continue或者出错)的时候,就会调用return方法,其次,return方法必须返回一个对象。
至于throw方法,则是用于抛出错误,Generator.prototype.throw这里不展开讲了,感兴趣的可以搜索一下。
2、用ES6新功能Generator函数来实现Symbol.iterator接口,事半功倍。
var yieldIterator = {};
yieldIterator[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...yieldIterator] // [1, 2, 3]
注意,yield* 后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
对Generator不了解的可以戳 ES6 Generator的语法 ,这里不多说。
3、至于可以使用Array.from转换成数组的类数组对象,部署iterator有一种很简单的方法,即直接使用数组的[Symbol.iterator]接口。
fakeArray.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
四、默认调用Iterator接口的场景
1、解构赋值
2、扩展运算符(...)
3、上文提到的 yield*
4、由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,都默认调用,如
for...of
Array.from()
Map(), Set(), WeakMap(), WeakSet()
Promise.all()
Promise.race()
5、字符串是一个类似数组的对象,原生也具有Iterator接口