blog
blog copied to clipboard
JS 与迭代器
JavaScript 的历史上,先有两种集合,Array
和 Object
,ES6 中新加了两种集合,Map
和 Set
。遍历 Array
可以用 for 循环或 forEach
等 ES5 中提供的遍历方法,Object
使用 for...in
,Map
和 Set
使用 for...of
。可以预见,如果不添加一个统一的遍历接口,数据结构改变就需要不同的遍历方法,势必会越来越混乱。所以,ES6 中添加了迭代器(Iterator)这一机制,为不同的数据结构提供统一的访问接口。
什么是迭代器
所谓迭代器,就是一种封装,封装了获取集合中某个值的操作,而屏蔽了『如何拿到某个值』的细节。举个例子,如果想获取对象和数组的第一个值,数组的取值方法是 arr[0], 对象的取值方法是 for (const v in obj) obj[v]
。为了屏蔽差异,我们需要设计一种通用的方法。一种做法就是往这些集合上增加个方法,该方法可以拿到集合中的值,取值时只需要调用这个方法就可以了。
使用迭代器
ES6 中数组部署了迭代器,我们来看看如何使用迭代器遍历数组:
const arr = [1,2,3]
const iterator = arr[Symbol.iterator]()
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: undefined, done: true }
iterator.next() // { value: undefined, done: true }
上面短短几行中,有几点是需要注意的:
- Symbol 是 ES6 中引入的新全局变量,想详细了解的戳这里;
- Symbol.iterator 是 Symbol 的一个属性,每个可遍历的集合上都有这个键;
- arr[Symbol.iterator] 返回的是一个函数,arrSymbol.iterator 返回一个对象,对象中包含 next 方法,用来遍历集合;
- next() 方法从头遍历集合,返回的不是值,而是一个有 value 和 done 字段的对象,done 表示遍历是否结束,结束后无论调用多少次 next,返回的永远是
{ value: undefined, done: true }
。
迭代器与 for...of
一遍一遍地写 .next() 相当低效,所以 ES6 引入了 for...of
帮我们自动遍历。for...of
会自动调用迭代器接口,类似这样:
const arr = [1,2,3]
for (const v of arr) {
console.log(v) // 1, 2, 3
}
自己实现个迭代器
四大集合,Array
、Set
、Map
都有迭代器接口,独独少了 Object
,据说是为了以后考虑。不过虽说官方没有默认提供,我们可以手动添加,自己实现个迭代器:
const foo = {
a: 1,
b: 2,
c: 3,
}
Object.defineProperty(foo, Symbol.iterator, {
configurable: false,
enumerable: false,
writable: false,
get: () => {
let index = -1
const values = Object.entries(foo)
const len = values.length
return () => ({
next: () => {
index++
return {
value: values[index] ? values[index][1] : undefined,
done: index >= len ? true : false,
}
}
})
}
})
for (const v of foo) {
console.log(v) // 1, 2, 3
}
done, for...of
识别出了我们自定义的迭代器!
迭代器与 generator
{ value: xx, done: false }
这样的对象感觉很眼熟... 我们来对比一下数组和 generator function:
const arr = [1,2,3]
const iterator = arr[Symbol.iterator]()
iterator.next() // { value: 1, done: false }
// ----------------------
function * iterFunc () {
let i = 1
while (true) {
yield i++
}
}
const gen = iterFunc()
gen.next() // { value: 1, done: false }
简直一毛一样。
而且,generator 函数还可以用来部署迭代器
function* makeIterator(obj){
var nextIndex = 0;
var values = Object.entries(obj)
while(nextIndex < values.length){
yield values[nextIndex++][1];
}
}
var gen = makeIterator({
a: 1,
b: 2,
});
for (const v of gen) {
console.log(v); // 1, 2
}
迭代器的其他用途
其实 ES6 中迭代器无处不在,随便列举几个
rest 参数:
function rest (...args) {
for (const v of args) {
console.log(args)
}
}
解构赋值:
const [a, b] = 'a.b'.split('.')
const [c, d] = 'a.b.c'.split('.')
// c: 'a', d: ['b', 'c']
还可以对类数组比如字符串等操作:
[...'abc'] // 'a', 'b', 'c'
已撸