JavaScript解构赋值深拷贝对象

JavaScript 解构与对象复制:浅拷贝、深拷贝

讲清对象和数组解构、展开语法的浅拷贝边界,并比较 structuredClone、JSON 序列化与自定义深拷贝的适用场景。

·更新于 ·阅读约 11 分钟·计算中...

解构用于从对象或数组中取值;展开语法可以创建第一层新对象或新数组,但仍是浅拷贝;需要复制嵌套数据时优先评估 structuredClone,不要默认使用 JSON.parse(JSON.stringify(value))

本文合并旧版解构赋值和递归深拷贝文章。

目录

对象解构

const user = {
  id: 42,
  profile: { name: "Steven" },
  role: "editor",
}

const { id, role } = user

重命名和默认值:

const { role: userRole, locale = "zh-CN" } = user

默认值只在属性不存在或值为 undefined 时生效,null 不会触发默认值。

嵌套解构:

const {
  profile: { name },
} = user

如果 profile 可能不存在,这段会抛错。可以先验证数据,或使用安全默认结构:

const { profile: { name } = {} } = user

TypeScript 中空对象默认值可能需要明确类型,不应为了通过类型检查滥用断言。

数组解构

const [first, second] = ["Mac", "Windows", "Linux"]
const [head, ...rest] = [1, 2, 3, 4]

交换变量:

let left = "L"
let right = "R"
;[left, right] = [right, left]

无分号风格下,独立的 ([ 赋值表达式前需要注意自动分号插入。

展开语法是浅拷贝

const original = {
  name: "FreeMac",
  settings: { theme: "dark" },
}

const copy = { ...original }
copy.name = "Other"                 // 不影响 original.name
copy.settings.theme = "light"       // 会影响 original.settings.theme

copy 是新对象,但嵌套 settings 仍引用同一个对象。数组展开、slice()Array.from()Object.assign() 也只复制第一层。

更新嵌套不可变数据时,逐层复制发生变化的路径:

const next = {
  ...original,
  settings: {
    ...original.settings,
    theme: "light",
  },
}

structuredClone

现代浏览器和 Node.js 支持 structuredClone

const source = {
  createdAt: new Date(),
  tags: new Set(["Mac", "Free"]),
  map: new Map([["theme", "dark"]]),
  nested: { enabled: true },
}

const cloned = structuredClone(source)

它能处理循环引用和多种内建类型,也支持 transferable objects。但函数、DOM 节点等不能按普通业务对象直接克隆,类实例的原型和私有语义也不应想当然地保留。

克隆前要问:真的需要复制整棵数据吗?大型对象深拷贝会消耗时间和内存,状态管理通常只需复制变化路径。

JSON 深拷贝的限制

const copied = JSON.parse(JSON.stringify(value))

它只适用于明确可 JSON 序列化的数据,并会有明显损失:

  • Date 变为字符串。
  • undefined、函数和 Symbol 可能丢失。
  • MapSet 等无法按原类型恢复。
  • BigInt 不能直接序列化。
  • 循环引用会抛错。
  • NaN、Infinity 等值不能按原语义保留。

如果数据本来就是准备发送到 JSON API 的纯 JSON 结构,可以使用序列化;不要把它包装成通用 deepClone

为什么自定义递归 clone 容易出错

看似简单的递归函数通常遗漏:

  • 循环引用。
  • 属性描述符、Symbol key 和不可枚举属性。
  • Date、RegExp、Map、Set、ArrayBuffer。
  • 稀疏数组和原型。
  • getter 执行时的副作用。

只有业务数据结构非常明确且经过测试时才编写专用复制函数,并用名称说明它复制的具体模型,不要承诺支持任意 JavaScript 值。

解构不是复制全部数据

const { profile } = user

这里只把 user.profile 的引用赋给变量,没有创建新 profile。rest 属性会创建新的外层对象,但内部仍是浅拷贝:

const { id, ...withoutId } = user

选择建议

需求 推荐
从对象/数组读取几个值 解构
更新一层普通对象 展开语法
React state 更新嵌套字段 逐层复制变化路径
复制支持 structured clone 的完整数据 structuredClone
数据明确是 JSON JSON 序列化/解析
复制复杂业务类实例 由该类型提供明确转换方法

数组的转换、遍历与修改方法见 JavaScript 数组方法指南,安全访问可选字段可参考 可选链操作符

参考资料

订阅 FreeMac

每周精选:免费 Mac 软件评测、可信来源更新、替代方案和少折腾指南。