概念
可迭代是一种数据结构,可迭代对象的原型上都有一个Symbol.iterator方法,这是迭代器的工厂方法。
可迭代的对象有
- Arrays
- Strings
- Maps
- Sets
- DOM data structures (work in progress)
Array
for (const x of [1, 2, 3]) {
console.log(x)
}
// 1
// 2
// 3
String
// 遍历Unicode的时候,每个value值可能包含一个或两个JavaScript字符
for (const x of 'a\uD83D\uDC0A') {
console.log(x)
}
// a
// 🐊
注意:原始值也可以迭代,所以不是一定要是一个对象才是可迭代的,这是因为在访问迭代器方法【Symbol.iterator】之前,字符串已经被转为对应的包装对象了。
Maps
const map = new Map().set('a', 1).set('b', 2);
for (const pair of map) {
console.log(pair);
}
// ['a', 1]
// ['b', 2]
注意: WeakMaps不可迭代。
Sets
const set = new Set().add('a').add('b');
for (const x of set) {
console.log(x);
}
// 按添加的顺序迭代
// 'a'
// 'b'
注意:WeakSets不可迭代
arguments
function printArgs() {
for (const x of arguments) {
console.log(x);
}
}
printArgs('a', 'b');
// 'a'
// 'b'
DOM 数据结构
for (const node of document.querySelectorAll('div')) {
···
}
可变计算数据
const arr = ['a', 'b', 'c'];
for (const pair of arr.entries()) {
console.log(pair);
}
// Output:
// [0, 'a']
// [1, 'b']
// [2, 'c']
所有主要的ES6数据结构(Arrays, Typed Arrays, Maps, Sets)都有三个返回可迭代对象的方法:
entries()
返回一个可迭代的条目,编码为[key,value] 的Array。 对于Arrays,值是Array元素,键是它们的索引。 对于集合,每个键和值都相同 - Set元素。keys()
返回条目键的可迭代值。values()
返回条目值的可迭代值。
const arr = ['a', 'b', 'c'];
for (const pair of arr.entries()) {
console.log(pair);
}
// Output:
// [0, 'a']
// [1, 'b']
// [2, 'c']
普通对象不可迭代
for (const x of {}) { // TypeError
console.log(x);
}
如何迭代属性,可以根据迭代的原理自定义一个方法实现。
function objectEntries(obj) {
let index = 0
// In ES6, you can use strings or symbols as property keys,
// Reflect.ownKeys() retrieves both
const propKeys = Reflect.ownKeys(obj)
return {
[Symbol.iterator]() {
return this
},
next() {
if (index < propKeys.length) {
const key = propKeys[index]
index++
return { value: [key, obj[key]] }
} else {
return { done: true }
}
}
}
}
const obj = { first: 'Jane', last: 'Doe' }
for (const [key, value] of objectEntries(obj)) {
console.log(`${key}: ${value}`)
}
// Output:
// first: Jane
// last: Doe
另一种选择是使用迭代器而不是索引来遍历具有属性键的数组:
function objectEntries(obj) {
let iter = Reflect.ownKeys(obj)[Symbol.iterator]()
return {
[Symbol.iterator]() {
return this
},
next() {
let { done, value: key } = iter.next()
if (done) {
return { done: true }
}
return { value: [key, obj[key]] }
}
}
}
特点
- Data consumers(数据消费者): 使用for...of...,forEach循环或者展开运算符。
- Data sources(数据源): Arrays, Strings, Maps等
现在来看看,数组arr可以如何消费?首先通过 键为Symbol.iterator的方法,创建一个迭代器:
const arr = ['a', 'b', 'c'];
const iter = arr[Symbol.iterator]();
再通过迭代器的next()重复检索该数组中的每一项:
> iter.next()
{ value: 'a', done: false }
> iter.next()
{ value: 'b', done: false }
> iter.next()
{ value: 'c', done: false }
> iter.next()
{ value: undefined, done: true }
可以看到,next() 返回的每个项都会被包装在一个对象中,value 值为原数组中的项值,done 是否完成了该数组项序列的检索。
未完待续。。。