要避免循环引用导致的栈溢出,核心是用 weakmap 缓存已拷贝对象并按类型分层处理:基础类型直接返回;date、regexp 等构造新实例后缓存;map/set 递归拷贝键值;普通对象先占位再深拷贝;cache 必须显式传递防污染;优先使用原生 structuredclone。

要避免循环引用导致的栈溢出,核心不是阻止递归,而是提前识别并复用已拷贝的对象。关键在于用 WeakMap 做缓存映射,并按类型分层处理。
用 WeakMap 缓存已拷贝对象
WeakMap 是唯一适合的容器:键可以是任意对象,且不阻碍垃圾回收。每次进入新对象前,先查它是否已在缓存中:
- 查到了 → 直接返回对应副本,跳过后续递归
- 没查到 → 先新建空对象(或数组等),立刻存入 WeakMap,再继续遍历属性
- 这样 a → b → a 的环形结构,第二次遇到 a 就能命中缓存,自然中断递归链
分类处理不同类型值
不是所有值都要进 WeakMap 或递归。必须区分对待:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 基础类型(string、number、boolean、null、undefined、symbol、bigint):直接返回,不缓存
- Date、RegExp、ArrayBuffer、TypedArray:用 new Date(obj.getTime()) 等方式构造新实例,再存入 WeakMap 防重复
- Map / Set:新建实例后,递归拷贝其键和值(键也可能是对象,同样走 WeakMap 检查)
- 普通对象或数组:先占位,存进 WeakMap,再逐个属性深拷贝
缓存必须显式传递,不能设默认参数
常见错误是写成 function deepClone(obj, cache = {}) —— 这会让多次调用共用同一个 cache,造成跨调用污染。
- 正确做法:默认设
cache = null - 首次调用时初始化:
if (cache == null) cache = new WeakMap() - 所有递归调用都显式传入当前 cache 实例
优先考虑 structuredClone
如果你的环境支持(Chrome 98+、Node.js 17.0+、Firefox 98+、Safari 16.4+),structuredClone(obj) 是最稳妥的选择。
- 原生支持循环引用检测、Date、Map、Set、Blob、File 等类型
- 一行代码,语义清晰,性能可靠
- 注意:不支持函数、DOM 节点、Symbol(这些本就不该被深拷贝)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










