1. 问题背景
JavaScript 的解构赋值有两种形式:
- 数组解构
[]:要求右侧是可迭代对象(Iterable),即实现了Symbol.iterator接口 - 对象解构
{}:要求右侧是对象,按属性名匹配
当尝试用数组解构语法解构普通对象时:
js
var [a, b] = { a: 1, b: 2 }
// TypeError: {(intermediate value)} is not iterable
普通对象默认没有实现 Symbol.iterator,因此不能被数组解构。
2. 方案总览
本方案通过为对象添加 Symbol.iterator 方法,使其成为可迭代对象,从而支持数组解构语法。
js
// 给特定对象添加迭代器
const obj = { a: 1, b: 2 }
obj[Symbol.iterator] = function() {
return Object.values(this)[Symbol.iterator]()
}
// 现在可以用数组解构
var [a, b] = obj
console.log(a, b) // 1 2
3. 核心原理详解
3.1 可迭代协议(Iterable Protocol)
ECMAScript 规定,要成为可迭代对象,必须实现 Symbol.iterator 方法:
可迭代协议 :一个对象必须具有
Symbol.iterator属性,该属性是一个无参函数,返回一个迭代器对象(Iterator)。
js
const iterable = {
[Symbol.iterator]() {
// 返回迭代器对象
return {
next() {
// 返回 { value: any, done: boolean }
}
}
}
}
3.2 迭代器协议(Iterator Protocol)
迭代器对象必须实现 next() 方法,返回一个包含 value 和 done 的对象:
| 属性 | 类型 | 说明 |
|---|---|---|
value |
any | 当前迭代值 |
done |
boolean | true 表示迭代结束,false 表示继续 |
js
const iterator = {
next() {
return { value: 1, done: false }
}
}
3.3 数组解构的内部机制
当执行 var [a, b] = obj 时,JavaScript 引擎内部执行以下步骤:
ini
1. 获取 obj[Symbol.iterator]
2. 调用 iterator = obj[Symbol.iterator]()
3. 调用 result = iterator.next()
4. 赋值 a = result.value
5. 调用 result = iterator.next()
6. 赋值 b = result.value
7. 如果还有元素,继续调用 next()
如果 obj 没有 Symbol.iterator,步骤 1 就会抛出 TypeError: is not iterable。
4. 实现方案详解
4.1 基础实现:借用 Object.values()
js
const obj = { a: 1, b: 2 }
obj[Symbol.iterator] = function() {
return Object.values(this)[Symbol.iterator]()
}
var [a, b] = obj
console.log(a, b) // 1 2
逐行拆解
js
obj[Symbol.iterator] = function() {
// this 指向 obj
// Object.values(this) 返回 [1, 2]
// 数组原生实现了 Symbol.iterator,返回数组迭代器
return Object.values(this)[Symbol.iterator]()
}
关键点:
Object.values(this)提取对象的所有值,返回数组- 数组天然是可迭代对象,直接借用其迭代器
- 无需手动实现
next()方法
4.2 完整迭代器实现(不依赖数组)
js
const obj = { a: 1, b: 2 }
obj[Symbol.iterator] = function() {
const values = Object.values(this)
let index = 0
return {
next() {
if (index < values.length) {
return { value: values[index++], done: false }
}
return { done: true }
}
}
}
var [a, b] = obj
console.log(a, b) // 1 2
执行流程图解
perl
初始化:
values = [1, 2]
index = 0
第一次 next():
index(0) < 2 → true
return { value: values[0]=1, done: false }
index++ → 1
第二次 next():
index(1) < 2 → true
return { value: values[1]=2, done: false }
index++ → 2
第三次 next():
index(2) < 2 → false
return { done: true }
4.3 支持 for...of 循环
添加了 Symbol.iterator 的对象,不仅支持数组解构,还支持所有可迭代协议的消费方式:
js
const obj = { a: 1, b: 2, c: 3 }
obj[Symbol.iterator] = function() {
return Object.values(this)[Symbol.iterator]()
}
// 1. 数组解构
var [a, b, c] = obj
// 2. for...of 循环
for (const value of obj) {
console.log(value) // 1, 2, 3
}
// 3. 展开运算符
const arr = [...obj] // [1, 2, 3]
// 4. Array.from
const arr2 = Array.from(obj) // [1, 2, 3]
// 5. Set/Map 构造
const set = new Set(obj) // Set {1, 2, 3}
4.4 支持键值对迭代
如果需要解构出键值对,可以迭代 Object.entries():
js
const obj = { a: 1, b: 2 }
obj[Symbol.iterator] = function() {
return Object.entries(this)[Symbol.iterator]()
}
// 解构键值对
var [entry1, entry2] = obj
console.log(entry1) // ['a', 1]
console.log(entry2) // ['b', 2]
// 嵌套解构
var [[key1, val1], [key2, val2]] = obj
console.log(key1, val1) // 'a', 1
console.log(key2, val2) // 'b', 2
5. 进阶实现
5.1 自定义迭代顺序
js
const obj = { z: 1, a: 2, m: 3 }
// 按键名排序后迭代
obj[Symbol.iterator] = function() {
const sortedKeys = Object.keys(this).sort()
let index = 0
return {
next: () => {
if (index < sortedKeys.length) {
const key = sortedKeys[index++]
return { value: this[key], done: false }
}
return { done: true }
}
}
}
var [a, b, c] = obj
console.log(a, b, c) // 2, 3, 1(按 a, m, z 顺序)
5.2 迭代器生成器函数(Generator)
js
const obj = { a: 1, b: 2 }
obj[Symbol.iterator] = function* () {
for (const key of Object.keys(this)) {
yield this[key]
}
}
var [a, b] = obj
console.log(a, b) // 1, 2
Generator 优势:
- 代码更简洁,自动管理状态
- 自动实现
next()和done逻辑 - 支持
yield*委托其他可迭代对象
5.3 类级别实现
js
class IterableObject {
constructor(data) {
this.data = data
}
*[Symbol.iterator]() {
for (const key of Object.keys(this.data)) {
yield this.data[key]
}
}
}
const obj = new IterableObject({ a: 1, b: 2 })
var [a, b] = obj
console.log(a, b) // 1, 2
6. 边界情况与注意事项
6.1 空对象
js
const obj = {}
obj[Symbol.iterator] = function() {
return Object.values(this)[Symbol.iterator]()
}
var [a, b] = obj
console.log(a, b) // undefined, undefined
数组解构对缺失值会赋 undefined,不会报错。
6.2 对象属性变更后迭代
js
const obj = { a: 1, b: 2 }
obj[Symbol.iterator] = function() {
return Object.values(this)[Symbol.iterator]()
}
// 添加新属性
obj.c = 3
var [a, b, c] = obj
console.log(a, b, c) // 1, 2, 3(动态反映最新属性)
每次迭代都会重新调用 Object.values(this),因此能反映对象的最新状态。
6.3 不要污染 Object.prototype
js
// ❌ 错误:污染全局原型
Object.prototype[Symbol.iterator] = function() {
return Object.values(this)[Symbol.iterator]()
}
// 影响所有对象,包括 {}、[]、function 等
// 可能导致不可预期的副作用
正确做法:只给特定对象实例添加迭代器。
7. 完整可运行代码
js
/**
* 为对象添加 Symbol.iterator 支持数组解构
* @param {Object} obj - 目标对象
* @returns {Object} - 返回原对象(链式调用)
*/
function makeIterable(obj) {
obj[Symbol.iterator] = function() {
return Object.values(this)[Symbol.iterator]()
}
return obj
}
// ========== 使用示例 ==========
// 示例 1: 基础数组解构
const obj1 = makeIterable({ a: 1, b: 2 })
var [a, b] = obj1
console.log(a, b) // 1 2
// 示例 2: 支持 for...of
const obj2 = makeIterable({ x: 10, y: 20, z: 30 })
for (const value of obj2) {
console.log(value) // 10, 20, 30
}
// 示例 3: 展开运算符
const obj3 = makeIterable({ name: 'Tom', age: 25 })
const arr = [...obj3]
console.log(arr) // ['Tom', 25]
// 示例 4: 键值对迭代
const obj4 = { a: 1, b: 2 }
obj4[Symbol.iterator] = function* () {
for (const entry of Object.entries(this)) {
yield entry
}
}
var [[k1, v1], [k2, v2]] = obj4
console.log(k1, v1, k2, v2) // 'a' 1 'b' 2
// 示例 5: 生成器实现
const obj5 = { first: 'Hello', second: 'World' }
obj5[Symbol.iterator] = function* () {
yield* Object.values(this)
}
var [greeting, subject] = obj5
console.log(greeting, subject) // 'Hello' 'World'
8. 一句话总结
通过为对象添加
Symbol.iterator方法(通常借用Object.values(this)[Symbol.iterator]()),可以将普通对象变为可迭代对象,从而支持数组解构、for...of、展开运算符等所有可迭代协议的消费方式。注意只给特定对象实例添加,避免污染Object.prototype。