遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费。
Iterator 的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,并且如果Symbol.iterator方法对应的不是遍历器生成函数,会报错
原生具备 Iterator 接口的数据结构如下。
Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象
我们可以把数组的Symbol.iterator属性赋给类数组,再用扩展运算符,实现类数组转数组
之前写过自己实现Iterator接口
Object.defineProperty(myObject, Symbol.iterator, {
enumerable: false,
writable:false,
configurable:false,
value:function(){
let o = this;
let idx = 0;
let ks = Object.keys(o);
return {
next:function(){
return {
value:o[ks[idx++]],
done:(idx>ks.length)
}
}
}
}
});
其实还有一种更简单粗暴的实现方式,用Generator 函数
let myIterable = {
[Symbol.iterator]: function* () {
yield 1; //根据情况写表达式
yield 2;
yield 3;
}
}
[...myIterable] // [1, 2, 3]
遍历器next方法最重要,但除了它还有return和throw方法,return方法的使用场合是,如果for…of循环提前退出(通常是因为出错,或者有break语句或continue语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。
function readLinesSync(file) {
return {
[Symbol.iterator]() {
return {
next() {
return { done: false };
},
return() {
file.close();
return { done: true };
}
};
},
};
}
//触发return
// 情况一 输出文件的第一行以后,就会执行return方法,关闭这个文件
for (let line of readLinesSync(fileName)) {
console.log(line);
break;
}
// 情况二 输出所有行以后,执行return方法,关闭该文件
for (let line of readLinesSync(fileName)) {
console.log(line);
continue;
}
// 情况三 会在执行return方法关闭文件之后,再抛出错误
for (let line of readLinesSync(fileName)) {
console.log(line);
throw new Error();
}
ES6 的数组、Set、Map 都部署了以下三个方法,调用后都返回遍历器对象(Object.keys等与他们不同,返回的是数组)
entries() 返回一个遍历器对象,用来遍历[键名, 键值]组成的数组。
keys() 返回一个遍历器对象,用来遍历所有的键名。
values() 返回一个遍历器对象,用来遍历所有的键值。