解构用于从对象或数组中取值;展开语法可以创建第一层新对象或新数组,但仍是浅拷贝;需要复制嵌套数据时优先评估 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 可能丢失。Map、Set等无法按原类型恢复。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 数组方法指南,安全访问可选字段可参考 可选链操作符。
参考资料
继续阅读
JavaScript Event Loop:任务、微任务与渲染顺序
通过调用栈、任务队列、微任务检查点和浏览器渲染流程,解释 Promise、queueMicrotask、setTimeout 与 async/await 的真实执行顺序和常见性能陷阱。
13 分钟JavaScript 数组与遍历:map、splice、for of
按是否修改原数组梳理 map、filter、reduce、find、slice、splice、sort、toSorted,以及 for、forEach、for of、for in 的选择方法。
7 分钟JavaScript % 余数运算:负数、取模与实用场景
解释 JavaScript % 返回余数而非数学取模,展示负数归一化、奇偶判断、循环索引、角度归一化和 BigInt 的正确写法。
订阅 FreeMac
每周精选:免费 Mac 软件评测、可信来源更新、替代方案和少折腾指南。