You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免对象相互引用时深拷贝出现无限递归?

如何避免深拷贝递归中的循环引用无限递归问题

这个问题绝对是手写深拷贝时最容易踩的“深坑”之一!循环引用(比如A引用B、B又引用A)导致无限递归的本质,就是我们的递归逻辑没记住已经处理过哪些对象,一直在反复处理相互引用的节点,直到栈溢出。

解决这个问题的核心思路非常清晰:用一个缓存容器来记录已经完成拷贝的对象,每次递归处理前先检查当前对象是否已经在缓存里——如果有,直接返回缓存的拷贝结果;如果没有,就先创建拷贝对象并存入缓存,再递归处理它的属性。

最优实现:用WeakMap做缓存

我最推荐用WeakMap作为缓存容器,因为它的键是对象的弱引用,不会阻止垃圾回收器回收那些不再被其他地方引用的原对象,能避免内存泄漏。下面是完整的实现代码:

function deepClone(obj, cache = new WeakMap()) {
  // 处理基本类型、null和undefined:直接返回原值
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // 先检查缓存:如果当前对象已经拷贝过,直接返回缓存的副本
  if (cache.has(obj)) {
    return cache.get(obj);
  }

  // 根据原对象的类型,创建对应的拷贝实例(保留原型链)
  let cloneObj;
  if (obj instanceof Array) {
    cloneObj = [];
  } else {
    // 普通对象用Object.create继承原对象的原型,确保拷贝后的对象原型链正确
    cloneObj = Object.create(Object.getPrototypeOf(obj));
  }

  // 将原对象和它的拷贝存入缓存,后续遇到相同引用直接用缓存
  cache.set(obj, cloneObj);

  // 遍历所有属性(包括不可枚举属性和Symbol类型的键),递归拷贝
  Reflect.ownKeys(obj).forEach(key => {
    cloneObj[key] = deepClone(obj[key], cache);
  });

  return cloneObj;
}

关键细节解释

  • WeakMap的优势:如果用普通Map的话,键是强引用,即使原对象已经没有其他引用,缓存里的强引用也会让它无法被垃圾回收,长期运行可能导致内存泄漏。WeakMap完美解决了这个问题。
  • 保留原型链:用Object.create(Object.getPrototypeOf(obj))创建普通对象的拷贝,而不是直接{},这样能保留原对象的原型链,拷贝出来的对象和原对象是同一个构造函数的实例。
  • 遍历所有属性:Reflect.ownKeys会返回对象的所有自有键,包括不可枚举属性和Symbol类型的键,比Object.keys或for...in更全面。

测试循环引用场景

我们用你提到的Person A和Person B的例子来测试:

const personA = { name: 'Alice' };
const personB = { name: 'Bob' };
personA.bestFriend = personB;
personB.bestFriend = personA;

const clonedA = deepClone(personA);
console.log(clonedA.bestFriend.bestFriend === clonedA); // 输出true,说明循环引用处理成功,没有无限递归

备选方案:用普通Map

如果你的运行环境不支持WeakMap(现在这种情况很少见了),也可以用普通Map替代,但要注意在拷贝完成后如果不需要缓存了,最好手动清空它,避免内存泄漏:

function deepCloneWithMap(obj, cache = new Map()) {
  // 逻辑和WeakMap版本几乎一致,只是把WeakMap换成Map
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  if (cache.has(obj)) {
    return cache.get(obj);
  }
  let cloneObj = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj));
  cache.set(obj, cloneObj);
  Reflect.ownKeys(obj).forEach(key => {
    cloneObj[key] = deepCloneWithMap(obj[key], cache);
  });
  return cloneObj;
}

总的来说,只要记住“用缓存记录已拷贝对象”这个核心,就能轻松解决循环引用导致的无限递归问题,同时还能避免重复拷贝同一个对象,提升深拷贝的性能。

内容的提问来源于stack exchange,提问作者melkors_lammoth

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 00:57:33