真正可靠的方式是手写递归深拷贝并用WeakMap检测循环引用,逐类型构造副本,只拷贝自有可枚举属性,忽略getter/setter,函数默认不拷贝。

直接用 JSON.parse(JSON.stringify(obj)) 会报错,因为循环引用无法被序列化;structuredClone() 虽能处理循环引用,但不支持函数、undefined、Symbol、访问器属性等。真正可靠的方式是手写递归深拷贝,并用 WeakMap 记录已处理对象。
用 WeakMap 检测并拦截循环引用
核心逻辑是:每次准备拷贝一个对象前,先查 WeakMap 是否已有该对象的拷贝结果。若有,直接返回,避免重复递归和栈溢出。
-
WeakMap键必须是对象引用,且不会阻止垃圾回收,比普通Map更安全 - 在创建新对象后、填充属性前就存入
WeakMap,确保递归进入子属性时能命中缓存 - 例如:
const a = {}; const b = { ref: a }; a.ref = b;,拷贝a时遇到b.ref指向a,此时查表返回已缓存的a拷贝体
逐类型判断并构造对应副本
不能只靠 typeof,要结合 instanceof 和专用检测方法,为每类值生成语义正确的拷贝:
-
null和基本类型(string/number/boolean/bigint/symbol)直接返回 -
Date→new Date(obj);RegExp→new RegExp(obj) -
Array→[]并遍历元素递归拷贝;Object→{}或保留构造器(new obj.constructor()) -
Map/Set需用entries()或values()获取内容,再新建实例填入
只拷贝自有可枚举属性,兼顾健壮性
避免污染原型链或漏掉关键数据:
- 用
Object.keys()或for...in+hasOwnProperty遍历,跳过继承属性 - 若需支持
Symbol属性,额外调用Object.getOwnPropertySymbols() - 不处理
getter/setter—— 它们不是“值”,手动实现会改变行为,通常应忽略 - 函数默认不拷贝(保持引用),如需复制函数体,需额外解析
fn.toString(),但极少必要
兼容老环境的简化版参考
以下函数覆盖常见场景(对象、数组、Date、RegExp、Map、Set),含循环引用防护,无依赖:
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (hash.has(obj)) return hash.get(obj);
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Map) {
const map = new Map();
hash.set(obj, map);
obj.forEach((v, k) => map.set(k, deepClone(v, hash)));
return map;
}
if (obj instanceof Set) {
const set = new Set();
hash.set(obj, set);
obj.forEach(v => set.add(deepClone(v, hash)));
return set;
}
const cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











