Object.assign和扩展运算符均只浅拷贝第一层属性,嵌套对象共享引用;区别在于前者触发setter、后者支持迭代器;深拷贝禁用JSON方案,应选structuredClone或lodash.cloneDeep等可靠方法。
Object.assign 还是扩展运算符?两者行为基本一致,都只复制第一层属性,嵌套对象仍共享引用。区别在于:Object.assign 会触发 setter,而扩展运算符不会;扩展运算符支持迭代器,Object.assign 只处理自有可枚举属性。
常见错误是以为 {...obj} 能“安全复制”整个对象,结果修改 obj.nested.value 时,副本也跟着变。
{...defaults, ...overrides})Object.assign(null, src) 会报错,必须传入第一个参数为对象JSON.parse(JSON.stringify(obj))
这招看着方便,但实际掉坑里的人最多。它会静默丢失:undefined、function、Symbol、BigInt、Date(变成字符串)、RegExp(变成空对象)、Map/Set(变成空对象),且无法处理循环引用——直接抛 TypeError: Converting circular structure to JSON。
真正需要深拷贝时,优先考虑成熟库的实现(如 Lodash 的 _.cloneDeep),或自己写一个带循环检测的递归函数。
WeakMap 缓存已遍历对象,避免无限递归
如高频渲染数据),避免在 render 周期中调用深拷贝structuredClone(现代浏览器支持)可处理 Map/Set/Date/RegExp,但暂不支持 function 和循环引用深浅拷贝本身不导致内存泄漏,但拷贝后的对象如果被意外保留在全局、定时器、事件监听器或闭包中,就可能让本该释放的内存一直挂着。
典型例子:组件卸载后,异步请求返回仍尝试更新已销毁组件的 state;或给 DOM 元素绑定监听器,却没在销毁时 removeEventListener。
addEventListener 时,尽量传入具名函数,方便后续移除;或用 { once: true } 自动清理setTimeout 回调闭包,改用 ID 或轻量标识符很多所谓“深拷贝需求”,本质是想避免副作用。与其无脑拷贝,不如从数据流设计入手:用不可变更新(如 Immer)、状态归一化(Redux Toolkit 的 createEntityAdapter)、或结构共享(如 Immutable.js 的持久化数据结构)。
拷贝成本随嵌套深度和字段数量指数上升;而一次浅拷贝 + 局部更新,往往比全量深拷贝更可控、更不易出错。