迭代器和for-of循环
从数组遍历谈起
最传统的数组遍历方法应该是以下这样:
|
|
这种方式看起来比较麻烦。
forEach()
后来ECMAScript5发布之后,我们可以使用forEach()方法来遍历数组:
|
|
它可以读取数组的键,也可以读取数组的值。
但是,它不可以在遍历过程中使用break、return来跳出循环,也不能使用return返回数据。
for-in
我们也曾使用for-in来遍历数组:
|
|
for-in循环用于遍历键(用字符串表示),而不是遍历值。
它有以下3个明显的缺点:
- 数组的键(索引)为数组,而for-in循环遍历的到的是字符串’0’,’1’,’2’…
- for-in除了遍历数组键名外,还会遍历数组的自定义键名,比如myArr.name,则name会被遍历出来
- 一些情况下,for-in会以任意顺序遍历键名。
for-of
ES6为我们提供了一种新的循环语法:for-of。
它的基本用法如下:
|
|
for-of避开了for-in循环的所有缺陷,它遍历的是数组的值。
并且,与forEach()不同,它可以正常响应break、continue和return语句。
|
|
对比
- for-in遍历键(字符串表示),适合用来遍历对象属性。
- for-of遍历值,并且可以遍历更多的数据类型(下面会讲到),并且可以配合break、continue、return等使用。
- forEach()可以遍历键和值,但是不能使用break、continue等退出循环,也不能使用return返回数据。
更强大的for-of
for-of循环其实主要为遍历可迭代对象(具有迭代器方法的一切对象)而生。
ES6规定,迭代器方法为[Symbol.iterator]()
方法。
所以,只要某个对象实现了[Symbol.iterator]()
的属性,那么这个对象就是可迭代对象,它就可以用for-of遍历。
在ES6中,有3类数据结构原生具有迭代器方法: 数组、某些类似数组的对象(NodeList、arguments、字符串)、Set和Map结构。
for-of遍历原理
for-of循环遍历某种数据结构时,会首先调用数据结构的访问器方法,即
[Symbol.iterator]()
方法,这个方法返回一个新的迭代器对象。迭代器对象是任何具有next()
方法的对象,它不同于可迭代对象。for-of循环将重复调用迭代器对象中的
next()
方法,每次循环调用一次。- 每次调用
next()
方法,都会返回一个包含value
和done
两个属性的对象,其中value
属性值为此次迭代的输出结果,而done
属性值是一个布尔值,表示遍历是否结束。
以下代码定义了一个很简单的可迭代对象:
|
|
遍历更多数据类型
除了遍历数组外,for-of还可以遍历更多的数据类型,包括Set和Map结构、类数据对象(比如arguments、DOM NodeList对象、字符串)、Generator对象。
我们来看以下几个场景:
|
|
遍历普通对象
for-of不能直接遍历普通对象,只有实现了迭代器方法[Symbol.iterator]()
才能使用。
这种情况下,我们可以使用for-in来遍历键名。
或者采用Object.keys()来将对象的键名生成一个数组,然后遍历这个数组,代码如下:
|
|
迭代器对象的return()和throw()
迭代器对象,除了具有next()
方法,还有return()
和throw()
方法。
其中next()方法是必须的,而return()和throw()方法时可选的。
return()方法的使用场合是:如果for-of循环因为出错,或者因为break、continue语句而提前退出,就会调用return()方法。
throw()方法主要是配合Generator函数使用,一般的迭代器对象用不到这个方法。
默认调用迭代器方法的场合
有一些场合会默认调用可迭代对象的迭代器方法,即[Symbol.iterator()]
方法,除了for-of循环,还有以下几个场合。
解构赋值
对数组和Set解构赋值时,会默认调用迭代器方法。以下代码证明了这一点:
|
|
也就是说,我们可以将任何具有迭代器方法的数据结构,进行解构赋值。
扩展运算符
扩展运算符也会默认调用迭代器方法:
|
|
也就是说,我们可以将任何具有迭代器方法的数据结构,通过扩展运算符,将它转化为数组。
其他场合
由于数组的遍历会调用迭代器方法,所以任何可以接收数组作为参数的场合,其实都调用了迭代器方法。下面是一些例子:
- Array.from()
- Map(),Set()
- Promise.all()
- Promise.race()