彻底理解js中的Iterables和Iterator

634 阅读2分钟

概念

可迭代是一种数据结构,可迭代对象的原型上都有一个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 是否完成了该数组项序列的检索。

未完待续。。。

juejin.cn/post/684490…