如何在javascript中实现深拷贝_有哪些常见的方法和陷阱?

JavaScript深拷贝需创建完全独立的新对象,常用方法有:JSON.parse(JSON.stringify())(限制多)、structuredClone()(现代推荐)、手写递归(灵活可控)、Lodash的_.cloneDeep()(生产稳妥)。

JavaScript 中实现深拷贝,核心是创建一个与原对象完全独立的新对象,所有嵌套层级的属性都复制一份,修改新对象不会影响原对象。最直接的方式不是简单赋值或 Object.assign,因为它们只做浅拷贝。

JSON.parse(JSON.stringify()) —— 快速但限制多

这是最常被用到的“一行深拷贝”写法,适合纯数据对象(仅含字符串、数字、布尔、null、数组、普通对象):

const original = { a: 1, b: [2, 3], c: { d: 4 } };
const copy = JSON.parse(JSON.stringify(original));

⚠️ 常见陷阱:

  • 函数、undefined、Symbol 类型会被忽略或丢失
  • Date、RegExp、Map、Set、BigInt 等内置对象会变成空对象或报错
  • 循环引用(对象内部引用自身)直接抛出异常
  • 原型链和 constructor 信息全部丢失

structuredClone() —— 现代浏览器推荐方案

ES2025 引入的原生 API,支持更多类型,且能处理循环引用:

const copy = structuredClone(original);

✅ 支持:Date、RegExp、ArrayBuffer、TypedArray、Map、Set、Blob、File、ImageData,以及循环引用。
❌ 不支持:函数、undefined、Symbol、Promise、window 对象等非可序列化值。

注意:Node.js 从 v17.0 开始支持,但需启用 --experimental-structured-cloning;v18.15+ 默认开启。浏览器兼容性需查 CanIUse(Chrome 98+、Firefox 94+、Safari 15.4+)。

手写递归深拷贝 —— 灵活可控但需谨慎

适用于需要自定义行为(如过滤字段、处理特殊类型、保留原型)的场景。关键点:

  • 先判断是否为 null 或基本类型,直接返回
  • 区分 Array、Date、RegExp、Map、Set 等类型,分别处理
  • 用 WeakMap 记录已拷贝的对象,避免循环引用死循环
  • 对普通对象遍历 ownKeys(含 Symbol),递归拷贝每个 value

示例简化版(不含 Map/Set/Date 等扩展):

function deepClone(obj, map = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (map.has(obj)) return map.get(obj);
  
  const clone = Array.isArray(obj) ? [] : {};
  map.set(obj, clone);
  
  for (const key of Reflect.ownKeys(obj)) {
    clone[key] = deepClone(obj[key], map);
  }
  return clone;
}

第三方库 —— 生产环境稳妥选择

Lodash 的 _.cloneDeep() 是最成熟的选择,覆盖几乎所有边界情况,包括函数(只拷贝引用)、正则、日期、Map/Set、不可枚举属性、循环引用等。

使用前安装:npm install lodash,然后:

import { cloneDeep } from 'lodash';
const copy = cloneDeep(original);

优点是稳定、文档全、社区验证充分;缺点是增加包体积(若项目极轻量,可考虑按需引入或精简版工具函数)。

不复杂但容易忽略:深拷贝永远有成本,嵌套越深、数据越大,性能开销越明显。实际开发中优先问自己——真的需要深拷贝吗?有时用不可变更新(如 Immer)或设计上避免突变更高效。