展开运算符(...)仅做浅层展开,不实现深拷贝:数组和对象展开均为浅拷贝,嵌套对象仍共享引用;多层手动展开属“伪深拷贝”,依赖已知结构,无法处理任意嵌套、循环引用或特殊对象。

展开运算符(...)本身不直接决定深拷贝或浅拷贝,它只是将可迭代对象“铺平”一层。是否发生深拷贝,取决于你用它操作的数据结构和上下文——它天然只做浅层展开,对嵌套对象/数组内部的引用不做递归复制。
数组展开:浅拷贝,但仅限第一层
对一维数组使用展开运算符,得到的是一个新数组,元素值被复制(对原始类型如数字、字符串是真复制;对对象、数组则是引用复制):
-
// 一维原始类型 → 安全的浅拷贝
const arr1 = [1, 2, 3];<br>const arr2 = [...arr1];<br>arr2[0] = 99;<br>// arr1 仍是 [1, 2, 3],互不影响
-
// 含嵌套对象 → 仍是浅拷贝:外层数组新,内层对象共享引用
const arr1 = [{ a: 1 }, { b: 2 }];<br>const arr2 = [...arr1];<br>arr2[0].a = 99;<br>// arr1[0].a 也变成 99 —— 因为 arr1[0] 和 arr2[0] 指向同一对象
对象展开:等价于 Object.assign(),仅浅拷贝
展开对象时,相当于把所有可枚举自身属性“一层展开”到新对象中,不处理嵌套:
-
// 简单属性 → 值被复制
const obj1 = { x: 1, y: "hello" };<br>const obj2 = { ...obj1 };<br>obj2.x = 99;<br>// obj1.x 仍为 1 -
// 嵌套对象属性 → 引用被复制,非深拷贝
const obj1 = { data: { id: 1 } };<br>const obj2 = { ...obj1 };<br>obj2.data.id = 99;<br>// obj1.data.id 同样变为 99
组合使用实现“伪深拷贝”(仅限已知结构)
当嵌套层级固定且简单时,可通过多层展开手动“打平”关键层级,但这不是通用深拷贝,而是有前提的显式展开:
立即学习“Java免费学习笔记(深入)”;
-
// 对两层嵌套数组,可逐层展开
const matrix = [[1, 2], [3, 4]];<br>const deepCopy = matrix.map(row => [...row]); // 每行单独展开
-
// 对两层对象,可嵌套展开
const user = { profile: { name: "Alice", settings: { theme: "dark" } } };<br>const copy = {<br> ...user,<br> profile: { ...user.profile, settings: { ...user.profile.settings } }<br>};
⚠️ 注意:这种写法依赖你清楚知道结构深度,无法应对任意嵌套或循环引用,也不适用于 Date、RegExp、Map 等特殊对象。
真正需要深拷贝时,展开运算符不够用
展开运算符无法替代成熟的深拷贝方案:
- JSON.parse(JSON.stringify(obj)):快但丢失函数、undefined、Symbol、Date、RegExp、循环引用,且会忽略原型和不可枚举属性
- Lodash 的
cloneDeep():健壮、支持各种类型和循环引用 - structuredClone()(现代浏览器):原生 API,支持 Map、Set、Date、RegExp、ArrayBuffer 等,但暂不支持函数和 undefined
不要试图用多个 ... 实现通用深拷贝——它设计初衷就是浅层解构与合并,不是序列化工具。


















