在前端 JavaScript 中,深拷贝遇到循环引用会导致栈溢出错误。解决这个问题的方法主要有以下几种:
1. 使用 WeakMap/WeakSet 追踪已拷贝对象:
这是最推荐的处理循环引用的方法。WeakMap
和 WeakSet
不会阻止垃圾回收,所以即使它们持有对象的引用,对象仍然可以被回收。
function deepClone(obj, cache = new WeakMap()) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (cache.has(obj)) {
return cache.get(obj); // 返回已拷贝的对象
}
let copy;
if (Array.isArray(obj)) {
copy = [];
} else if (obj instanceof Date) {
copy = new Date(obj);
} else if (obj instanceof RegExp) {
copy = new RegExp(obj);
} else {
copy = {};
}
cache.set(obj, copy); // 将原始对象和拷贝对象添加到 WeakMap
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
copy[key] = deepClone(obj[key], cache); // 递归拷贝属性值
}
}
return copy;
}
let original = {
a: 1,
b: {
c: 2,
},
};
original.b.d = original; // 创建循环引用
let cloned = deepClone(original);
console.log(cloned); // 深拷贝后的对象,没有栈溢出错误
console.log(cloned.b.d === cloned); // true,保持了循环引用关系
2. 使用 JSON.stringify 和 JSON.parse (有局限性):
这种方法简单快捷,但有几个重要的局限性:
- 无法拷贝函数、Date、RegExp 等非 JSON 安全的对象。 这些对象会被转换为 null 或其他基本类型。
- 无法处理循环引用。 遇到循环引用会抛出错误。 如果你的数据结构确定没有循环引用,或者可以接受丢失循环引用部分的数据,那么可以使用这种方法。
function simpleDeepClone(obj) {
try {
return JSON.parse(JSON.stringify(obj));
} catch (error) {
console.error("Error cloning object:", error);
return null; // 或其他默认值
}
}
3. 使用 lodash 的 _.cloneDeep()
方法:
Lodash 是一个流行的 JavaScript 工具库,提供了 _.cloneDeep()
方法,可以高效地进行深拷贝,并且能够处理循环引用。
const _ = require('lodash');
let original = { /* ... */ }; // 你的对象
let cloned = _.cloneDeep(original);
选择哪种方法?
- 对于需要处理循环引用,并且需要保留所有数据类型(包括函数、Date、RegExp 等)的情况,推荐使用 WeakMap/WeakSet 方法。
- 对于简单的对象,并且不包含循环引用和特殊数据类型,可以使用 JSON 方法。 这也是性能最高的方法。
- 对于需要处理各种复杂情况,并且希望使用成熟的库,可以使用 lodash 的
_.cloneDeep()
方法。
总而言之,理解不同方法的优缺点,并根据实际情况选择合适的方法至关重要。 避免在不了解数据结构的情况下盲目使用 JSON.stringify/parse
方法,因为它可能会导致数据丢失或错误。 使用 WeakMap/WeakSet
方法可以更安全、更完整地进行深拷贝。