JSON.parse(JSON.stringify(obj)) 不是真正深拷贝,会丢失 undefined、function、Symbol 等类型且不支持循环引用;structuredClone() 是更优原生方案,但不支持 function 和 undefined;手写需注意类型识别与循环引用处理;Lodash cloneDeep 兼容性好但体积大。

JSON.parse(JSON.stringify(obj)) 为什么不能直接用
它是最常被误用的“深拷贝”方案,表面看能复制嵌套对象,但实际漏掉太多东西:undefined、function、Symbol、Date、RegExp、Map、Set、BigInt 全部丢失或报错。比如 JSON.stringify({ a: undefined }) 得到 {};JSON.stringify(/abc/) 得到 {};遇到循环引用直接抛 TypeError: Converting circular structure to JSON。
适用场景仅限于纯数据对象(POJO),且确认不含上述类型。别在表单数据、配置项、状态树里无脑套用。
structuredClone() 是目前最靠谱的原生方案
Chrome 98+、Firefox 94+、Safari 15.4+ 已支持,能正确处理 Date、RegExp、Map、Set、ArrayBuffer、TypedArray、BigInt,也支持循环引用。但它不支持 function 和 undefined(会跳过或报错),也不能克隆带有不可枚举属性或原型方法的对象。
用法很简单:
立即学习“Java免费学习笔记(深入)”;
const cloned = structuredClone(originalObj);
注意两点:
– 它是同步操作,大数据量时可能阻塞主线程;
– 在 Web Worker 中可用,但在某些 iframe 或跨域环境里受限(受 structuredClone 的传输能力约束)。
手写递归实现要注意哪些边界
自己写深拷贝函数时,核心不是“递归”,而是“类型识别 + 分支处理”。常见疏漏点:
-
null必须单独判断,否则typeof null === 'object'会进错分支 -
Date和RegExp要用构造函数重建:new Date(obj)、new RegExp(obj) -
ArrayBuffer需用obj.slice(0)或new obj.constructor(obj),不能直接遍历 - 循环引用必须缓存已拷贝的引用,用
WeakMap记录源对象 → 目标对象映射,每次进入前先查 -
Map和Set要用构造函数初始化,再逐项set()或add(),不能只展开键值
不推荐为项目从零手写,除非明确知道要支持哪些类型、放弃哪些特性。
Lodash 的 cloneDeep 适合什么场景
它覆盖了绝大多数 JS 常见类型,包括 function(浅拷贝引用)、undefined(保留)、Error、Promise(只拷贝状态,不复制执行逻辑)等。但它体积大(约 7KB gzip 后),且对自定义类实例默认只拷贝可枚举属性,不调用 constructor 或 toJSON。
如果你的项目已引入 Lodash,或者需要兼容老浏览器(IE11)、处理含私有字段的类实例,cloneDeep 是稳妥选择。但若只是轻量状态管理,用 structuredClone 更干净。
另外注意:Lodash v4 的 cloneDeep 不处理 prototype 上的方法,v5 改为默认忽略非枚举属性——这点容易和预期不符。
深拷贝从来不是“选一个函数就行”的事,关键在清楚你要复制什么、舍弃什么、运行在哪种环境。很多所谓“性能问题”,其实源于对数据结构的误判,比如给一个带 10 层嵌套的 Map 套 JSON.parse/stringify,不如先想想——真需要全量拷贝吗?











