JavaScript深拷贝彻底切断引用关联的四种方案

 更新时间:2026年05月08日 09:25:51   作者:yqcoder  
文章讨论了JavaScript中的深拷贝问题,首先解释了赋值操作和浅拷贝的概念,然后介绍了JSON序列化、结构化克隆API、手写递归深拷贝、第三方库Lodash等四种深拷贝方案,最后强调了深拷贝的常见陷阱与注意事项,并给出了推荐使用场景和建议,需要的朋友可以参考下

在 JavaScript 中,赋值操作对于引用类型(Object, Array 等)只是复制了内存地址(指针)。
这意味着两个变量指向堆内存中的同一个对象

  • 浅拷贝(Shallow Copy):只复制第一层属性。如果属性值是对象,则复制其引用。
  • 深拷贝(Deep Copy):递归复制所有层级。在堆内存中开辟一块全新的空间,存放完全独立的数据副本。

1. 为什么需要深拷贝?

先看一个经典的“事故现场”:

const original = {
  name: "Lingma",
  info: {
    age: 1,
    skills: ["JS", "Vue"],
  },
};

// ❌ 错误做法:直接赋值或浅拷贝
const copy = { ...original }; // 或者 Object.assign({}, original)

// 修改副本中的嵌套对象
copy.info.age = 99;

console.log(original.info.age); // 99 😱 原数据也被修改了!

原因... 展开运算符只复制了 original 的第一层。info属性是一个对象,复制的是它的引用地址copy.infooriginal.info 指向堆内存中的同一个对象

深拷贝的目标:让 copyoriginal 在内存中完全独立,互不影响。

2. 方案一:JSON 序列化(最快但有限制)

这是最简单、最常用的“一行代码”深拷贝方法。

代码实现

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

copy.b.c = 99;
console.log(original.b.c); // 2 ✅ 互不影响

优点

  • 极简:无需引入库,无需手写逻辑。
  • 快速:浏览器底层 C++ 优化,性能通常优于手写递归。

缺点(致命缺陷)

它会丢失转换以下数据类型:

  1. undefined:会被忽略。
  2. Function:会被忽略。
  3. Symbol:会被忽略。
  4. Date:会变成字符串(ISO 格式),不再是 Date 对象。
  5. RegExp:会变成空对象 {}
  6. NaN, Infinity:会变成 null
  7. 循环引用:直接报错 TypeError: Converting circular structure to JSON

适用场景:纯数据对象(POJO),不包含函数、日期、正则等特殊类型。

3. 方案二:结构化克隆 APIstructuredClone(现代首选)

ES2022 引入的全局 API,旨在解决 JSON 方法的局限性。

代码实现

const original = {
  date: new Date(),
  map: new Map([["key", "value"]]),
  set: new Set([1, 2]),
  nested: { a: 1 },
};

const copy = structuredClone(original);

copy.nested.a = 99;
console.log(original.nested.a); // 1 ✅

console.log(copy.date instanceof Date); // true ✅ (JSON 方法做不到)

优点

  • 原生支持:无需 polyfill(现代浏览器已支持)。
  • 类型保留:支持 Date, Map, Set, RegExp, ArrayBuffer 等。
  • 处理循环引用:不会报错,能正确处理环形结构。

缺点

  • 不支持函数:依然无法拷贝 Function。
  • 兼容性:旧版浏览器(如 IE)不支持,需检查环境。

适用场景:现代项目,需要拷贝包含 Date/Map/Set 但不含函数的复杂对象。

4. 方案三:手写递归深拷贝(面试必考,最灵活)

在面试中,面试官通常会要求你手写一个深拷贝函数,以考察你对递归、类型判断和边界情况的处理。

核心思路

  1. 判断类型:如果是基本类型,直接返回。
  2. 处理特殊对象:Date, RegExp 等需特殊构造。
  3. 处理循环引用:使用 WeakMap 缓存已拷贝的对象,防止死循环。
  4. 递归拷贝:遍历对象/数组的每个属性,递归调用自身。

代码实现

function deepClone(obj, hash = new WeakMap()) {
  // 1. 处理 null 或非对象类型
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  // 2. 处理循环引用:如果已经拷贝过,直接返回缓存
  if (hash.has(obj)) {
    return hash.get(obj);
  }

  // 3. 处理特殊内置对象
  if (obj instanceof Date) {
    return new Date(obj.getTime());
  }
  if (obj instanceof RegExp) {
    return new RegExp(obj.source, obj.flags);
  }

  // 如果需要支持 Map/Set,可在此添加逻辑
  // if (obj instanceof Map) { ... }
  // if (obj instanceof Set) { ... }

  // 4. 创建新容器(保持原型链)
  // 使用 Object.create 可以保留原型链,比 {} 或 [] 更严谨
  const cloneObj = Array.isArray(obj)
    ? []
    : Object.create(Object.getPrototypeOf(obj));

  // 5. 存入缓存,防止循环引用
  hash.set(obj, cloneObj);

  // 6. 递归拷贝所有属性
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }

  return cloneObj;
}

测试循环引用

const a = { val: 1 };
a.self = a; // 循环引用

const b = deepClone(a);
console.log(b.val); // 1
console.log(b.self === b); // true ✅ 正确保持了内部引用关系

优点

  • 可控:你可以决定如何处理函数、Symbol、DOM 节点等。
  • 兼容性好:可在任何 JS 环境中运行。
  • 面试加分项:展示了对 WeakMap、原型链和递归的深刻理解。

缺点

  • 性能较差:递归开销大,深层嵌套对象可能栈溢出。
  • 代码复杂:需要考虑很多边界情况。

5. 方案四:第三方库 Lodash

在生产环境中,如果项目已经引入了 Lodash,直接使用 _.cloneDeep 是最稳妥的选择。

import _ from "lodash";

const copy = _.cloneDeep(original);

优点

  • 健壮:经过多年社区验证,处理了绝大多数边缘情况。
  • 省心:无需自己维护拷贝逻辑。

缺点

  • 包体积:如果只为这一个功能引入整个 Lodash,会增加打包体积(建议使用 lodash.clonedeep 按需引入)。

6. 常见陷阱与注意事项

函数无法被深拷贝
函数是执行代码的逻辑,不是数据。通常深拷贝会忽略函数,或者你只能拷贝函数的字符串表示(fn.toString()),但这失去了闭包上下文。

DOM 节点不能深拷贝
DOM 节点包含大量浏览器内部状态,不能用 JSON 或普通递归拷贝。如需拷贝 DOM,请使用原生 API node.cloneNode(true)

性能考量
深拷贝是非常昂贵的操作。对于大型数据(如几万条列表),频繁深拷贝会导致页面卡顿。

优化建议:尽量使用不可变数据(Immutable Data)思维,只在必要时拷贝;或使用结构性共享(如 Immutable.js, Immer)。

Getter/Setter 丢失
普通的递归拷贝可能会丢失属性的 get/set 描述符。如果需要保留,需使用 Object.getOwnPropertyDescriptorObject.defineProperty

总结

方案优点缺点推荐指数适用场景
JSON简单、快丢失函数/Date/undefined,不支持循环引用⭐⭐⭐纯数据对象,后端交互数据
structuredClone原生、支持多种类型、支持循环引用不支持函数,兼容性需注意⭐⭐⭐⭐⭐现代浏览器环境首选
手写递归灵活、可定制、面试必备性能一般,代码复杂⭐⭐⭐⭐面试、特殊定制需求
Lodash稳健、功能全增加包体积⭐⭐⭐⭐已使用 Lodash 的项目

博主寄语
深拷贝不是银弹。
在现代前端开发中,我们更推崇**不可变数据(Immutability)**的理念。
与其每次修改都深拷贝,不如使用 Immer 这样的库,通过 Proxy 代理来实现“看似可变,实则不可变”的高效状态管理。

记住选型原则

  1. 简单数据用 JSON
  2. 现代环境用 structuredClone
  3. 复杂定制手写信。
  4. 生产环境靠 Lodash 或 Immer。

以上就是JavaScript深拷贝彻底切断引用关联的四种方案的详细内容,更多关于JavaScript深拷贝切断引用关联的资料请关注脚本之家其它相关文章!

相关文章

最新评论