让对象支持数组解构赋值

通过实现迭代器协议,让普通对象支持数组解构语法

问题

如何让 var [a, b] = {a: 1, b: 2} 这样的解构赋值成功?

解答

问题分析

直接对对象使用数组解构会报错:

const obj = {
    a: '1',
    b: '2',
}

const [a, b] = obj // TypeError: obj is not iterable

错误提示对象不可迭代。数组和 Map 等内置类型默认是可迭代的,而普通对象不是。

可迭代协议

要成为可迭代对象,需要实现 @@iterator 方法,即对象上要有一个 [Symbol.iterator] 属性,它是一个函数,返回一个迭代器对象。

验证数组确实有这个方法:

const array = [1, 2, 3]
const iterator = array[Symbol.iterator]()

console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 2, done: false }
console.log(iterator.next()) // { value: 3, done: false }
console.log(iterator.next()) // { value: undefined, done: true }

迭代器对象的 next() 方法返回 { value, done },其中 value 是当前值,done 表示是否迭代完成。

数组解构的本质

const array = [1, 2, 3]
var [a, b, c] = array

// 本质上等同于
const iterator = array[Symbol.iterator]()
var a = iterator.next().value
var b = iterator.next().value
var c = iterator.next().value

解决方案

给对象添加 [Symbol.iterator] 方法:

const obj = {
    a: '1',
    b: '2',
    [Symbol.iterator]() {
        let index = 0
        const keys = Object.keys(this)
        return {
            next: () => {
                if (index < keys.length) {
                    return {
                        done: false,
                        value: this[keys[index++]]
                    }
                }
                return {
                    done: true,
                    value: undefined
                }
            }
        }
    }
}

const [a, b] = obj
console.log(a, b) // '1' '2'

现在对象也可以用 for...of 遍历:

for (let i of obj) {
    console.log(i)
}
// '1'
// '2'

关键点

  • 数组解构本质是调用对象的 [Symbol.iterator] 方法获取迭代器,然后依次调用 next() 获取值
  • 迭代器的 next() 方法返回 { value, done } 格式的对象
  • 普通对象默认不可迭代,需要手动实现 [Symbol.iterator] 方法
  • 实现迭代器后,对象也可以使用 for...of 循环