javascript如何实现深拷贝_有哪些方法和陷阱【教程】

10次阅读

jsON.parse(json.Stringify()) 不能用于生产环境,因其会丢失 dateregexpundefinedfunctionsymbol、BigInt 等类型,并无法处理循环引用;仅限纯 JSON 安全结构的临时调试。

javascript如何实现深拷贝_有哪些方法和陷阱【教程】

JSON.parse(JSON.stringify()) 为什么不能用在生产环境

它看似简单直接,但会默默丢失很多关键信息。遇到 DateRegExpundefinedfunctionSymbolBigInt 或循环引用时,要么报错,要么变成 NULL 或空对象

比如:JSON.stringify({ d: new Date(), f() {} }) 返回 {"d":"2024-01-01T00:00:00.000Z"} —— 函数没了,Date 变成字符串,再 parse 就只是个普通字符串,不是 Date 实例。

实操建议:

  • 仅限临时调试或确定数据是纯 JSON 安全结构(即只含 nullBooleannumberstringArrayObject,且无循环)时快速使用
  • 永远不要用于表单状态、配置对象、API 响应缓存等真实业务场景
  • 如果必须用,加一层 try...catch 并记录降级日志

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

是的,但得看运行环境。它是浏览器node.js(v17.0+ 启用 --experimental-structured-clone,v18.15+ 默认启用)提供的标准深拷贝 API,支持 DatemapSetRegExpArrayBufferTypedArrayBigInt 和循环引用,且不序列化为字符串。

立即学习Java免费学习笔记(深入)”;

但它不支持 functionundefinedSymbol —— 这些值会被忽略(不是报错),而且无法自定义处理逻辑。

实操建议:

  • 检查目标环境是否支持:typeof structuredClone === 'function'
  • 对含 function 的对象(如 vue/react 组件选项、带方法的类实例),不能依赖它
  • node.js 中若版本低于 18.15,需启动参数或改用其他方案
  • 它比手写递归快,也比 JSON 方案更安全,适合多数现代前端项目的数据层拷贝

手写递归实现深拷贝要注意哪些关键点

核心是识别类型、处理循环引用、保留原型链(可选)、跳过不可枚举或特殊属性。漏掉任一环节都可能引发静默错误或内存爆炸。

常见陷阱:

  • typeof 判断对象类型不准 —— typeof null === 'object'typeof [] === 'object',必须结合 Object.prototype.toString.call()Array.isArray()
  • 没缓存已遍历对象 → 遇到循环引用直接溢出
  • 直接用 new obj.constructor() 复制实例 → 构造函数可能有副作用,或不接受无参调用
  • 忽略 Map/Set 的键值对顺序或引用关系 → 拷贝后行为异常

一个最小可行骨架示例(仅示意逻辑):

function deepClone(obj, cache = new WeakMap()) {   if (obj === null || typeof obj !== 'object') return obj;   if (cache.has(obj)) return cache.get(obj);      const cloned = Array.isArray(obj) ? [] : {};   cache.set(obj, cloned);      for (const key in obj) {     if (Object.prototype.hasOwnProperty.call(obj, key)) {       cloned[key] = deepClone(obj[key], cache);     }   }   return cloned; }

Lodash 的 cloneDeep() 在什么情况下会出问题

它覆盖了绝大多数边界情况,但仍有几个隐蔽坑点:

  • 对带有自定义 toJSON() 方法的对象,会优先调用该方法再拷贝返回值,而不是按原结构深拷贝 —— 这可能导致意外截断(例如某些 ORM 模型对象)
  • 拷贝 promiseGeneratorProxy 等非标准可枚举对象时,结果是空对象或浅拷贝,文档里没强调这点
  • 如果对象含有大量嵌套且深度超过 V8 调用限制(约 10k 层),仍会抛 RangeError: Maximum call stack size exceeded
  • 打包体积:单独为一个 cloneDeep 引入整个 Lodash,在轻量项目里可能得不偿失

如果你的项目已用 Lodash,它仍是当前最省心的选择;但若只是需要深拷贝,建议评估是否真要承担它的体积和隐式行为。

真正难的从来不是“怎么拷”,而是“拷完还像原来那样工作”——类型、引用、构造逻辑、不可枚举属性、环境兼容性,缺一不可。随便挑一个没处理好的点,上线后就可能表现为某个表单提交丢失时间、某个图表渲染错乱、某次撤销操作回退失败。

text=ZqhQzanResources