17370845950

javascript如何实现深拷贝_有哪些方法和注意事项【教程】
JSON.pa

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

JSON.parse(JSON.stringify(obj)) 为什么不能直接用

它是最常被误用的“深拷贝”方案,表面看能复制嵌套对象,但实际漏掉太多东西:undefinedfunctionSymbolDateRegExpMapSetBigInt 全部丢失或报错。比如 JSON.stringify({ a: undefined }) 得到 {}JSON.stringify(/abc/) 得到 {};遇到循环引用直接抛 TypeError: Converting circular structure to JSON

适用场景仅限于纯数据对象(POJO),且确认不含上述类型。别在表单数据、配置项、状态树里无脑套用。

structuredClone() 是目前最靠谱的原生方案

Chrome 98+、Firefox 94+、Safari 15.4+ 已支持,能正确处理 DateRegExpMapSetArrayBufferTypedArrayBigInt,也支持循环引用。但它不支持 functionundefined(会跳过或报错),也不能克隆带有不可枚举属性或原型方法的对象。

用法很简单:

const cloned = structuredClone(originalObj);

注意两点:
– 它是同步操作,大数据量时可能阻塞主线程;
– 在 Web Worker 中可用,但在某些 iframe 或跨域环境里受限(受 structuredClone 的传输能力约束)。

手写递归实现要注意哪些边界

自己写深拷贝函数时,核心不是“递归”,而是“类型识别 + 分支处理”。常见疏漏点:

  • null 必须单独判断,否则 typeof null === 'object' 会进错分支
  • DateRegExp 要用构造函数重建:new Date(obj)new RegExp(obj)
  • ArrayBuffer 需用 obj.slice(0)new obj.constructor(obj),不能直接遍历
  • 循环引用必须缓存已拷贝的引用,用 WeakMap 记录源对象 → 目标对象映射,每次进入前先查
  • MapSet 要用构造函数初始化,再逐项 set()add(),不能只展开键值

不推荐为项目从零手写,除非明确知道要支持哪些类型、放弃哪些特性。

Lodash 的 cloneDeep 适合什么场景

它覆盖了绝大多数 JS 常见类型,包括 function(浅拷贝引用)、undefined(保留)、ErrorPromise(只拷贝状态,不复制执行逻辑)等。但它体积大(约 7KB gzip 后),且对自定义类实例默认只拷贝可枚举属性,不调用 constructortoJSON

如果你的项目已引入 Lodash,或者需要兼容老浏览器(IE11)、处理含私有字段的类实例,cloneDeep 是稳妥选择。但若只是轻量状态管理,用 structuredClone 更干净。

另外注意:Lodash v4 的 cloneDeep 不处理 prototype 上的方法,v5 改为默认忽略非枚举属性——这点容易和预期不符。

深拷贝从来不是“选一个函数就行”的事,关键在清楚你要复制什么、舍弃什么、运行在哪种环境。很多所谓“性能问题”,其实源于对数据结构的误判,比如给一个带 10 层嵌套的 MapJSON.parse/stringify,不如先想想——真需要全量拷贝吗?