手写深拷贝
实现支持循环引用和特殊类型的深拷贝函数
问题
手写一个深拷贝函数,要求:
- 处理循环引用
- 正确拷贝 Date、RegExp、Symbol 等特殊类型
- 支持数组和普通对象
解答
基础版本
function deepClone(obj, map = new WeakMap()) {
// 处理 null 和非对象类型
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理循环引用
if (map.has(obj)) {
return map.get(obj);
}
// 处理 Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// 处理 RegExp
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// 创建新对象或数组
const clone = Array.isArray(obj) ? [] : {};
// 存入 map,处理循环引用
map.set(obj, clone);
// 递归拷贝属性
for (const key of Reflect.ownKeys(obj)) {
clone[key] = deepClone(obj[key], map);
}
return clone;
}
完整版本(支持更多类型)
function deepClone(obj, map = new WeakMap()) {
// 处理 null 和非对象类型
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理循环引用
if (map.has(obj)) {
return map.get(obj);
}
// 获取对象类型
const type = Object.prototype.toString.call(obj);
// 处理特殊类型
switch (type) {
case '[object Date]':
return new Date(obj.getTime());
case '[object RegExp]':
return new RegExp(obj.source, obj.flags);
case '[object Map]': {
const clone = new Map();
map.set(obj, clone);
obj.forEach((value, key) => {
clone.set(deepClone(key, map), deepClone(value, map));
});
return clone;
}
case '[object Set]': {
const clone = new Set();
map.set(obj, clone);
obj.forEach(value => {
clone.add(deepClone(value, map));
});
return clone;
}
case '[object ArrayBuffer]':
return obj.slice(0);
}
// 处理数组和普通对象
const clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone);
// 使用 Reflect.ownKeys 获取所有属性(包括 Symbol)
for (const key of Reflect.ownKeys(obj)) {
clone[key] = deepClone(obj[key], map);
}
return clone;
}
测试用例
// 测试循环引用
const obj1 = { name: 'test' };
obj1.self = obj1;
const cloned1 = deepClone(obj1);
console.log(cloned1.self === cloned1); // true
console.log(cloned1 !== obj1); // true
// 测试特殊类型
const obj2 = {
date: new Date('2024-01-01'),
regex: /test/gi,
[Symbol('key')]: 'symbol value',
map: new Map([['a', 1]]),
set: new Set([1, 2, 3])
};
const cloned2 = deepClone(obj2);
console.log(cloned2.date instanceof Date); // true
console.log(cloned2.date !== obj2.date); // true
console.log(cloned2.regex.test('test')); // true
关键点
- WeakMap 处理循环引用:用 WeakMap 存储已拷贝对象,遇到重复引用直接返回
- Reflect.ownKeys 获取所有键:包括 Symbol 类型的键,
Object.keys无法获取 Symbol - 特殊对象单独处理:Date、RegExp、Map、Set 需要用各自的构造函数重新创建
- 递归拷贝:对象属性值可能还是对象,需要递归处理
- 类型判断用 toString:
Object.prototype.toString.call()比instanceof更准确
目录