实现完整的深拷贝
实现一个能够处理各种数据类型和循环引用的深拷贝函数
问题
在 JavaScript 中,对象和数组的赋值是引用传递,修改新对象会影响原对象。深拷贝需要创建一个完全独立的副本,包括处理:
- 基本数据类型和引用类型
- 数组、对象、Date、RegExp、Map、Set 等特殊对象
- 循环引用问题
- 原型链的保留
解答
function deepClone(obj, hash = new WeakMap()) {
// 处理 null 或 undefined
if (obj === null || obj === undefined) {
return obj;
}
// 处理基本数据类型
if (typeof obj !== 'object') {
return obj;
}
// 处理 Date 对象
if (obj instanceof Date) {
return new Date(obj);
}
// 处理 RegExp 对象
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// 处理 Map 对象
if (obj instanceof Map) {
const mapClone = new Map();
obj.forEach((value, key) => {
mapClone.set(key, deepClone(value, hash));
});
return mapClone;
}
// 处理 Set 对象
if (obj instanceof Set) {
const setClone = new Set();
obj.forEach(value => {
setClone.add(deepClone(value, hash));
});
return setClone;
}
// 处理循环引用
if (hash.has(obj)) {
return hash.get(obj);
}
// 创建新对象,保留原型链
const cloneObj = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj));
// 存储到 hash 中,用于处理循环引用
hash.set(obj, cloneObj);
// 处理 Symbol 类型的 key
const symbolKeys = Object.getOwnPropertySymbols(obj);
symbolKeys.forEach(key => {
cloneObj[key] = deepClone(obj[key], hash);
});
// 递归拷贝对象的所有属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
使用示例
// 示例 1: 基本对象拷贝
const obj1 = {
name: '张三',
age: 25,
hobbies: ['reading', 'coding'],
address: {
city: '北京',
district: '朝阳区'
}
};
const clonedObj1 = deepClone(obj1);
clonedObj1.address.city = '上海';
console.log(obj1.address.city); // 输出: 北京
console.log(clonedObj1.address.city); // 输出: 上海
// 示例 2: 处理循环引用
const obj2 = { name: '李四' };
obj2.self = obj2;
const clonedObj2 = deepClone(obj2);
console.log(clonedObj2.self === clonedObj2); // 输出: true
// 示例 3: 处理特殊对象
const obj3 = {
date: new Date('2024-01-01'),
regex: /test/gi,
map: new Map([['key1', 'value1'], ['key2', 'value2']]),
set: new Set([1, 2, 3]),
symbol: Symbol('test')
};
const clonedObj3 = deepClone(obj3);
console.log(clonedObj3.date instanceof Date); // 输出: true
console.log(clonedObj3.regex.test('TEST')); // 输出: true
console.log(clonedObj3.map.get('key1')); // 输出: value1
// 示例 4: 处理 Symbol 类型的 key
const symbolKey = Symbol('id');
const obj4 = {
[symbolKey]: 123,
name: '王五'
};
const clonedObj4 = deepClone(obj4);
console.log(clonedObj4[symbolKey]); // 输出: 123
关键点
- 使用 WeakMap 处理循环引用:WeakMap 的 key 是弱引用,不会阻止垃圾回收,避免内存泄漏
- 类型判断要全面:需要处理 Date、RegExp、Map、Set 等特殊对象类型
- 保留原型链:使用
Object.create(Object.getPrototypeOf(obj))保留对象的原型 - 处理 Symbol 类型的 key:使用
Object.getOwnPropertySymbols()获取 Symbol 类型的属性 - 使用 hasOwnProperty 过滤:只拷贝对象自身的属性,不拷贝原型链上的属性
- 递归处理嵌套结构:对每个属性值递归调用 deepClone 函数
- 边界条件处理:正确处理 null、undefined 和基本数据类型
目录