Object.assign和扩展运算符均只浅拷贝第一层属性,嵌套对象共享引用;区别在于前者触发setter、后者支持迭代器;深拷贝禁用JSON方案,应选structuredClone或lodash.cloneDeep等可靠方法。

浅拷贝用 Object.assign 还是扩展运算符?
两者行为基本一致,都只复制第一层属性,嵌套对象仍共享引用。区别在于:Object.assign 会触发 setter,而扩展运算符不会;扩展运算符支持迭代器,Object.assign 只处理自有可枚举属性。
常见错误是以为 {...obj} 能“安全复制”整个对象,结果修改 obj.nested.value 时,副本也跟着变。
- 适合场景:配置合并、单层对象临时覆盖(如
{...defaults, ...overrides}) - 不适用:含 Date、RegExp、Map、Set 或循环引用的对象
- 注意:
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),或自己写一个带循环检测的递归函数。
立即学习“Java免费学习笔记(深入)”;
- 若必须手写,务必用
WeakMap缓存已遍历对象,避免无限递归 - 对性能敏感场景(如高频渲染数据),避免在 render 周期中调用深拷贝
- 注意
structuredClone(现代浏览器支持)可处理 Map/Set/Date/RegExp,但暂不支持 function 和循环引用
内存泄漏常藏在事件监听和闭包里
深浅拷贝本身不导致内存泄漏,但拷贝后的对象如果被意外保留在全局、定时器、事件监听器或闭包中,就可能让本该释放的内存一直挂着。
典型例子:组件卸载后,异步请求返回仍尝试更新已销毁组件的 state;或给 DOM 元素绑定监听器,却没在销毁时 removeEventListener。
- 使用
addEventListener时,尽量传入具名函数,方便后续移除;或用{ once: true }自动清理 - React 中 useEffect 清理函数必须返回一个函数,且该函数内不应再捕获外部变量(尤其大型对象),否则闭包持有引用不释放
- 避免把整个响应数据塞进
setTimeout回调闭包,改用 ID 或轻量标识符
复杂对象拷贝前先问:真的需要拷贝吗?
很多所谓“深拷贝需求”,本质是想避免副作用。与其无脑拷贝,不如从数据流设计入手:用不可变更新(如 Immer)、状态归一化(Redux Toolkit 的 createEntityAdapter)、或结构共享(如 Immutable.js 的持久化数据结构)。
拷贝成本随嵌套深度和字段数量指数上升;而一次浅拷贝 + 局部更新,往往比全量深拷贝更可控、更不易出错。
- DOM 节点、Canvas 上下文、WebGL 对象等不能被拷贝,强行序列化会失败
- WebSocket、EventSource 实例也不可拷贝,它们是运行时资源,不是纯数据
- 最隐蔽的泄漏点:拷贝后把新对象挂到某个长期存活的 Map/Set 中,却忘了清理逻辑










