优雅的遍历:迭代器
在ECMAScript6之前,常用for循环
来遍历数组,但是这种方式需要定义一个索引
来记录每一次迭代的位置。这在多层for循环遍历时,会增加遍历的复杂度,且容易出错。为了更方便地进行迭代,ECMAScript6引入了迭代器(Iterator
)。
什么是迭代器
迭代器并非JavaScript独创,在Python
和Java
等语言中,都有迭代器。那么在JavaScript中,迭代器到底长什么样呢?
在ECMAScript 6中,已经默认为一些类型提供了内置
迭代器。对于集合对象来说,数组
、Map
和Set
这三种类型都内建了以下三种迭代器:
- entries() : 返回一个迭代器,其值为多个键值对
- values():返回一个迭代器,其值为集合的值
- keys():返回一个迭代器,其值为集合中的所有键名
const arr = [100, 'abc', 200]
const it = arr.values()
console.log(it)
console.log(it.next())
console.log(it.next())
console.log(it.next())
console.log(it.next())
arr.values()
会返回一个迭代器,长这个样子:
它有一个next()
方法,每次调用都会迭代一次数据,获得的结果是一个对象(包含一个value属性和一个done属性),当所有数据都迭代完成后,最终返回的对象为{value: undefined, done: true}
。
上面代码的迭代结果如下:
黄金搭档:for-of
for-of
循环,这才是真正提高生产力的工具。for-of
循环,遍历就变成了一个简单清晰的任务,我们来看一个小例子:const arr = [100, 'abc', 200]
const it = arr.values()
for (let value of it) {
console.log(value)
}
请注意:for-of循环是用在迭代器上的,不是用在数组上的。但是,如果直接用在数组上,似乎也没问题:
const arr = [100, 'abc', 200]
for (let value of arr) {
console.log(value)
}
二者结果完全一样,数组本身并不是迭代器,那为啥可以使用for-of呢?
这是因为JavaScript引擎
获得了迭代器(会自动调用arr数组原型对象上的Symbol.iterator
方法,结果是返回一个迭代器)。
如果将for-of语句用于不可迭代的对象,会导致程序抛出异常:
const obj = {
name: 'foo',
age: 20
}
for (let value of obj) {
console.log(value)
}
obj对象并不是一个可迭代的对象,所以执行时出现了错误:Uncaught TypeError: obj is not iterable
。
实际应用
最后,说说在实际开发过程中,常用的一些迭代器技巧。
对于数组、Set和Map,可以直接使用for-of来进行迭代。除此之外,字符串
和NodeList
也可以使用for-of来迭代:
const msg = 'abc'
for (let c of msg) {
console.log(c)
}
const divs = document.getElementsByTagName("div")
for (let div of divs) {
console.log(div)
}
另外,展开运算符(...)
可以操作所有可迭代对象。
展开运算符
来看一个例子:
const set = new Set([100, 200])
const arr = ['aa', 'bb']
const result = [...set, ...arr, 300]
展开运算符会将所有可迭代对象进行展开,所以,上面的结果会到了一个新数组[100, 200, 'aa', 'bb', 300]
。
小结
现在,相信大家已经对迭代器有了大概的理解了,但是有一个问题,上面所见的都是语言内置的迭代器对象,如果让要一个普通对象也可以迭代,需要怎么做呢?或者需要自定义迭代器,怎么办呢?请见下一篇《迭代器工厂:生成器》。
参考
- 深入理解ES6,Nicholas C. Zakas