你不知道的for...of
ES6提供了一种新的遍历方法for...of,对于遍历数组,for...in遍历的是数组的索引,而for...of则是遍历数组的元素。事实上,当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator (遍历器)接口。默认的Iterator 接口部署在数据结构的Symbol.iterator属性,而没有默认Iterator接口的数据结构则需要开发者手动指定。
什么是Iterator
在js中,表示集合概念的有四种数据结构: 对象(Object)、数组(Array)、Set和Map,用户可以单独使用其中一种数据集合,或者是组合使用,ES6为此提供一种统一的接口机制,用来处理所有不同的数据结构,这种机制就是遍历器(Iterator)。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
Iterator 的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
从此可以看出:遍历器对象本质上,就是一个指针对象
下面我们模拟一个Iterator接口:
function iterator(arr) {
let nextIndex = 0;
return {
next: function () {
return nextIndex < arr.length ?
{
value: arr[nextIndex++],
done: false
} :
{
value: undefined,
done: true
};
},
};
}
const it = iterator([1, 2, 3]);
console.log(it.next()); // {value: 1, done: false}
console.log(it.next()); // {value: 2, done: false}
console.log(it.next()); // {value: 3, done: false}
console.log(it.next()); // {value: undefined, done: true}上面代码定义了一个iterator函数,它是一个遍历器生成函数,作用就是生成一个遍历器对象。对数组[1, 2, 3]执行这个函数,就会返回该数组的遍历器对象(即指针对象)it。
指针对象it包含一个next()方法,这个方法没调用一次,都会返回一个包含value属性和done属性的对象,同时指针移到下一个位置。it.next()返回的对象包表示当前数据成员的信息,value返回当前数据成员,done是一个布尔值,表示遍历是否结束。
默认的Iterator接口
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。
Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性。
为了验证默认的Iterator接口是否和我们之前模拟的接口一样,我们可以调用数组的Iterator
判断数据是否可以用for...of遍历,只需要判断是否有Symbol.iterator即可
不过我们平时写代码时很少这样做,因为数组、map、set这些数据结构原生具备 Iterator 接口,即不用任何处理,就可以被for...of循环遍历。
原生具备 Iterator 接口的数据结构如下。
Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象
对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。一个对象如果要具备可被for...of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。
为对象手写一个Iterator接口
原生对象(Object)是没有默认Iterator的,因此如果要用for...of方法遍历,需要我们为对象添加一个Iterator接口。
哪些场合会用到Iterator
有一些场合会默认调用 Iterator 接口(即Symbol.iterator方法):
数组和 Set 结构进行解构赋值时
扩展运算符(...)
yield*
for...of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(比如
new Map([['a',1],['b',2]]))Promise.all()
Promise.race()
参考
《JavaScript高级程序设计第四版》
《深入理解ES6》
最后更新于