在ES6中,添加了Set和Map结构(ES6学习笔记(五)Set结构和Map结构),加上之前的数组和对象,一共有四种可以表示“集合”的数据结构,而通过组合使用它们可以定义自己的数据结构。为了处理不同的数据结构,需要一个统一的机制,而Iterator接口就是这样的机制。
Iterator接口的作用和遍历过程
作用:Iterator接口用于为不同的数据结构提供统一的接口,使得数据结构的成员能按一定的次序遍历,部署了Iterator接口的数据结构可以使用for...of来遍历。即Iterator接口就是为了实现不同数据结构的遍历,而遍历又是有序的,所以没有部署Iterator接口的数据结构成员在部署Iterator接口后就从无序变为线性有序了。
遍历过程:Iterator接口的遍历过程是通过指针来进行的,它首先将指针指向第一个位置,然后通过遍历器中的next方法来将指针移动到下一个位置(部署Iterator接口时一定要部署next方法),通过done(布尔值)来判断是否已经遍历到尾部,当done为true时,遍历结束。
下面是一个遍历器的模拟方法
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
makeIterator方法用来模拟一个遍历器,一开始将指针指向下标为0的位置,每次调用next方法移动到下一个位置前判断当前是否到了数组的尾部,如果还没有,返回value为下一个值,done为false的对象,如果到了尾部,返回value为undefined,done为true的对象。
默认的Iterator接口
默认的Iterator接口部署在Symbol.Iterator属性,只要结构有这个属性,就是可以遍历的,而这个属性本身是一个方法,如果调用该方法,会返回一个遍历器,而可以通过该遍历器的next方法来遍历结构中的每个值,知道done为true
ES6中具备Iterator接口的数据结构有:Array,Map,Set,String,TypeArray,arguments对象,NodeList对象
var arr=[1,2,3];
var i=arr[Symbol.iterator]();
i.next();//{value: 1, done: false}
i.next();//{value: 2, done: false}
i.next();//{value: 3, done: false}
i.next();//{value: undefined, done: false}
部署Iterator接口
要遍历一个没有Iterator接口的数据结构,需要在Symbol.iterator属性上部署遍历器方法(或者在原型链上部署该方法)
class RangeIterator {
constructor(start, stop) {//初始化遍历的起点和终点
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() {
return this;
}//返回该遍历器
next() {
var value = this.value;//取得当前值
if (value < this.stop) {//判断当前是否走到了结尾
this.value++;
return {done: false, value: value};
}
return {done: true, value: undefined};
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for (var value of range(0, 3)) {
console.log(value); // 0, 1, 2
}
上面的代码部署了一个iterator接口,通过其Symbol.iterator方法来u后去其遍历器对象,然后用for...of来遍历该结构
function Obj(value) {
this.value = value;
this.next = null;//一开始的指针指向null
}
Obj.prototype[Symbol.iterator] = function() {
var iterator = { next: next };
var current = this;
function next() {
if (current) {//如果没有知道next的话,current为null,所以这里用于判断是否到了遍历的最后
var value = current.value;
current = current.next;//移到下一个位置
return { done: false, value: value };
} else {
return { done: true };
}
}
return iterator;
}
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);
one.next = two;
two.next = three;
for (var i of one){
console.log(i); // 1, 2, 3
}
上面的代码声明了一个Obj方法,通过在原型链上的Symbol.iterator上部署iterator接口,在实例上调用Symbol.iterator方法时返回遍历器对象。
对于类似数组,可以直接将其Symbol.iterator方法改为直接引用数组的Iterator接口
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// 或者NodeList.prototype[Symbol.iterator] = [][Symbol.iterator]
普通对象部署数组的Symbol.iterator方法,并无效果
Object.prototype[Symbol.iterator]=Array.prototype[Symbol.iterator];
let o=new Object({a:1,b:2});
for (let i of o)
console.log(i);//undefined
上面的代码虽然给Object对象部署了数组的Symbol.iterator方法,但是用for...of遍历时返回了undefined。
给Symbol.iterator部署方法时要注意,返回的必须是一个遍历器,如果返回了其他结构或者没有返回,浏览器会报错。
let o={a:1}
o[Symbol.iterator]=()=>1;
for (let i of o)
console.log(i);
自动调用iterator接口
在部署iterator接口后的数据结构和有默认iterator接口的数据在使用中,其实有些地方会自动调用到iterator接口
1.解构赋值
在对数组和Set结构进行解构赋值时,会默认调用Symbol.Iterator方法,我的理解是,数组和Set结构要进行解构赋值,需要获取里面每一个值,需要使用遍历操作,所以会默认调用Symbol.Iterator方法。
2.扩展运算符
扩展运算符是通过iterator接口遍历数据结构,将里面的成员取出来后以逗号分隔
3.yield*
Yield*后面跟一个可遍历的结构,会调用该结构的遍历器接口
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.数组的遍历
在遍历数组的时候实际上就是调用了iterator接口,所以任何接受数组为参数的场合都默认调用iterator接口,下面几个例子都会调用iterator接口
- for...of
- Array.from()
- Map(), Set(), WeakMap(), WeakSet()
- Promise.all()
- Promise.race()
5.Set和Map结构的遍历方法
Set和Map结构在调用keys,values,entries方法时会返回一个遍历器对象。(SetIterator和MapIterator)
遍历器对象的其他方法
遍历器除了next方法外,还有return方法和throw方法,如果要自己部署iterator接口,next方法是必要的,但是return方法和throw方法可以不要。
return方法是在循环中途要退出遍历的时候调用。
throw方法是用于配合Generator函数
JavaScript的几种遍历方法
for循环
for (let i=0;i<arr.length;i++)
console.log(i);
for循环写起来较其他遍历方法会繁琐一点,需要声明一个用来遍历的变量,还需要写遍历结束条件以及步长。
forEach
arr.forEach(i=>console.log(i));
forEach虽然在遍历时比较方便,但不能在中途跳出遍历,break语句和return语句都无效
for...in
for (let i in arr)
console.log(i);
for..in会遍历数组的键名,但因为键名是字符串,所以只能返回字符串,所以for...in并不适合用于遍历数组,而适合用于遍历对象
for...of
for (let i of arr)
console.log(i);
for...of有和for...in一样简洁的写法,而且没有for...in的缺点,同时也可以使用break语句和return语句停止遍历。
参考自阮一峰的《ECMAScript6入门》