var [a, b] = { a: 1, b: 2 } 解构赋值

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() 方法,返回一个包含 valuedone 的对象:

属性 类型 说明
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

相关推荐
用户059540174461 小时前
Playwright 网络拦截踩坑实录:我花了 3 小时才搞懂数据持久化验证的正确姿势
前端·css
weedsfly1 小时前
React 开发中的闭包陷阱:四个真实场景,让你彻底理解闭包
前端·react.js
MariaH1 小时前
Git Cherry Pick 常用操作
前端
初圣魔门首席弟子1 小时前
AI Agent 核心原理:工具调用(Function Calling)完整工作流程详解
前端·数据库·人工智能
CodeSheep1 小时前
又是梁文锋,有点猛啊。
前端·后端·程序员
陈老老老板1 小时前
如何用 Bright Data Web Scraper API + Coze 搭建 Reddit 行业情报聚合 Bot(2026 实战指南)
前端·人工智能
恋猫de小郭1 小时前
由于 iOS 26 的键盘变化,Flutter 又要重构键盘区域逻辑
android·前端·flutter
怕浪猫2 小时前
Electron 开发实战(十五):实战项目|从零搭建桌面即时通讯(IM)应用
前端·javascript·electron