深度克隆(Deep Clone)是指复制一个对象或数组及其所有嵌套结构的副本,使得克隆后的对象与原对象完全独立。JavaScript 提供了一些方法实现深度克隆,但每种方法有其优缺点。
1. 常用方法
1.1 使用 JSON.parse
和 JSON.stringify
这种方法最简单,但有局限性。
const obj = { a: 1, b: { c: 2 } };
const clone = JSON.parse(JSON.stringify(obj));
console.log(clone); // { a: 1, b: { c: 2 } }
console.log(clone === obj); // false
console.log(clone.b === obj.b); // false
优点
- 简单易用。
- 适用于纯 JSON 格式的数据。
缺点
- 不支持函数、
undefined
、Symbol
、循环引用等。 - 日期对象会变成字符串,
RegExp
对象会丢失。
1.2 使用 structuredClone
structuredClone
是一种内置的深拷贝方法,支持复杂数据结构。
const obj = { a: 1, b: { c: 2 }, d: new Date() };
const clone = structuredClone(obj);
console.log(clone); // { a: 1, b: { c: 2 }, d: Date }
console.log(clone === obj); // false
console.log(clone.b === obj.b); // false
优点
- 支持更多类型(如
Date
、RegExp
、Map
、Set
等)。 - 处理循环引用。
缺点
- 不支持旧版本浏览器。
1.3 使用递归实现深度克隆
通过递归遍历对象和数组,手动实现深拷贝。
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) {
return new Date(obj);
}
if (obj instanceof RegExp) {
return new RegExp(obj);
}
const clone = Array.isArray(obj) ? [] : {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
const obj = { a: 1, b: { c: 2 }, d: new Date() };
const clone = deepClone(obj);
console.log(clone); // { a: 1, b: { c: 2 }, d: Date }
console.log(clone === obj); // false
console.log(clone.b === obj.b); // false
优点
- 完全控制克隆逻辑。
- 支持特定类型的处理。
缺点
- 不支持循环引用,需额外处理。
2. 循环引用处理
对于有循环引用的对象,需要使用 WeakMap
来避免递归陷入死循环。
function deepClone (value, cache = new WeakMap ()) {
if (typeof value !== "object" || value === null) {
return value;
}
if (cache.has(value)) {
return cache.get(value);
}
const result = Array.isArray(value) ? [] : {};
Object.setPrototypeOf(result, Object.getPrototypeOf(value));
cache.set(value, result);
for (i in value) {
if (value.hasOwnProperty(i)) {
result[i] = deepClone(value[i], cache);
}
}
return result;
}
const obj = { a: 1 };
obj.b = obj; // 循环引用
let clone = deepClone(obj);
console.log(clone); // { a: 1, b: [Circular] }
const Person = function(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.a = 100;
clone = new Person('a', 12)
console.log(clone);
3. 方法对比
方法 | 支持类型 | 处理循环引用 | 性能 | 易用性 |
---|---|---|---|---|
JSON.parse/stringify | 仅支持 JSON 格式 | 否 | 高 | 简单 |
structuredClone | 支持大部分类型 | 是 | 高 | 简单 |
手动递归 | 可定制支持类型 | 否(需扩展) | 中 | 中等 |
Lodash | 支持复杂结构和循环引用 | 是 | 中 | 简单 |
推荐
- 数据结构简单:使用
JSON.parse
和JSON.stringify
。 - 现代浏览器:使用
structuredClone
。 - 复杂场景:使用
Lodash
或手动实现带循环引用处理的深拷贝函数。