展开运算符{...obj}仅执行浅拷贝:基本类型值被独立复制,嵌套对象仍共享内存地址,故修改嵌套属性会同步影响原对象;深拷贝需用structuredClone()、JSON方法或Lodash.cloneDeep()等替代方案。

展开运算符 {...obj} 对嵌套对象不做任何特殊处理——它只复制第一层属性,内部对象依然共享引用。这意味着你无法靠它解决嵌套引用问题,必须主动干预。
为什么嵌套对象会被“连带修改”
因为展开运算符只做浅拷贝:基础类型(如字符串、数字)值被真正复制;而对象、数组等引用类型,只是把内存地址复制过去。新旧对象的嵌套部分指向同一块内存。
-
示例:若
original = { a: 1, user: { name: 'Alice' } },用copy = { ...original }后,copy.user === original.user为true - 改
copy.user.name,original.user.name也会变 - 但改
copy.a或新增copy.b,不影响原对象
想让嵌套对象也独立,得换方法
展开运算符本身不提供深拷贝能力,需搭配其他策略:
-
简单纯数据结构:用
JSON.parse(JSON.stringify(obj)),但会丢函数、undefined、Date、RegExp、Symbol 和循环引用 -
现代浏览器环境:优先用
structuredClone(obj),支持 Symbol、Map、Set、Date、RegExp,也能处理循环引用 -
工程级稳定需求:引入 Lodash 的
_.cloneDeep(),兼容性好,细节处理完善 -
可控定制场景:手动递归实现,或结合
Object.getOwnPropertyDescriptors()处理不可枚举属性和 Symbol 键
临时绕过嵌套问题的小技巧
如果只关心某一层嵌套,可以手动展开那部分:
const copy = { ...original, user: { ...original.user } };- 对多层嵌套,可逐层展开:
nested: { ...original.nested, profile: { ...original.nested.profile } } - 适合结构固定、层级少的场景,但不推荐用于动态或深层嵌套
别指望展开运算符自动深拷贝
它设计初衷就是轻量、高效的一层复制。依赖它处理嵌套,等于默认接受副作用风险。判断是否需要深拷贝,关键看后续会不会修改嵌套内容——会改,就必须换方案。

















