深拷贝是 JavaScript 开发中常见的需求,本文将带你从基础概念开始,逐步深入,最终实现一个完美的深拷贝函数。
一、浅拷贝 vs 深拷贝
1. 浅拷贝
浅拷贝只复制对象的第一层属性,更深层次的属性仍然是共享的。
const obj = { a: 1, b: { c: 2 } };
const shallowCopy = { ...obj };
shallowCopy.b.c = 3;
console.log(obj.b.c); // 3 - 原对象也被修改了
2. 深拷贝
深拷贝会递归复制对象的所有层级,创建完全独立的副本。
const obj = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(obj));
deepCopy.b.c = 3;
console.log(obj.b.c); // 2 - 原对象不受影响
局限性:
- 不能处理函数、undefined、Symbol
- 不能处理循环引用
- 会丢失 Date 对象(转为字符串)
- 会丢失 RegExp 对象
- 会丢失原型链
2. 递归实现(基础版)
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
let clone = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
问题:无法处理循环引用
三、处理循环引用
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理循环引用
if (hash.has(obj)) {
return hash.get(obj);
}
let clone = Array.isArray(obj) ? [] : {};
hash.set(obj, clone);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], hash);
}
}
return clone;
}
四、处理特殊对象
1. 处理 Date 和 RegExp
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理特殊对象
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (hash.has(obj)) return hash.get(obj);
//自定义对象处理
let clone = new obj.constructor();
hash.set(obj, clone);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], hash);
}
}
return clone;
}
2. 处理 Map 和 Set
function deepClone(obj, hash = new WeakMap()) {
// ...前面的代码...
if (obj instanceof Map) {
const clone = new Map();
hash.set(obj, clone);
obj.forEach((value, key) => {
clone.set(deepClone(key, hash), deepClone(value, hash));
});
return clone;
}
if (obj instanceof Set) {
const clone = new Set();
hash.set(obj, clone);
obj.forEach(value => {
clone.add(deepClone(value, hash));
});
return clone;
}
// ...后面的代码...
}
五、处理 Symbol 属性和原型链
function deepClone(obj, hash = new WeakMap()) {
// 处理原始值和null
if (Object(obj) !== obj || obj === null) return obj;
// 处理特殊对象
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Map) {
const clone = new Map();
hash.set(obj, clone);
obj.forEach((value, key) => {
clone.set(deepClone(key, hash), deepClone(value, hash));
});
return clone;
}
if (obj instanceof Set) {
const clone = new Set();
hash.set(obj, clone);
obj.forEach(value => {
clone.add(deepClone(value, hash));
});
return clone;
}
// 处理循环引用
if (hash.has(obj)) return hash.get(obj);
// 获取所有属性,包括Symbol
const allKeys = Reflect.ownKeys(obj);
// 获取原型
const proto = Object.getPrototypeOf(obj);
// 创建新对象,保持原型链
const clone = Object.create(proto);
hash.set(obj, clone);
// 复制所有属性
allKeys.forEach(key => {
clone[key] = deepClone(obj[key], hash);
});
return clone;
}
六、最终完美实现
function deepClone(obj, hash = new WeakMap()) {
// 处理原始值和null
if (Object(obj) !== obj || obj === null) return obj;
// 处理特殊对象
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Map) {
const clone = new Map();
hash.set(obj, clone);
obj.forEach((value, key) => {
clone.set(deepClone(key, hash), deepClone(value, hash));
});
return clone;
}
if (obj instanceof Set) {
const clone = new Set();
hash.set(obj, clone);
obj.forEach(value => {
clone.add(deepClone(value, hash));
});
return clone;
}
if (obj instanceof ArrayBuffer) return obj.slice(0);
if (ArrayBuffer.isView(obj)) {
return new obj.constructor(
deepClone(obj.buffer, hash),
obj.byteOffset,
obj.byteLength
);
}
// 处理循环引用
if (hash.has(obj)) return hash.get(obj);
// 处理普通对象和数组
const proto = Object.getPrototypeOf(obj);
const clone = Object.create(proto);
hash.set(obj, clone);
// 复制Symbol属性和普通属性
Reflect.ownKeys(obj).forEach(key => {
clone[key] = deepClone(obj[key], hash);
});
const allDesc = Object.getOwnPropertyDescriptors(obj)
const result = Object.create(Object.getPrototypeOf(obj), allDesc)
hash.set(obj, result)
return clone;
}
WeakMap 的基本作用
WeakMap 是 JavaScript 中的一种特殊集合类型,与普通 Map 的主要区别在于:
键必须是对象(不能是原始值)
键是弱引用(不会阻止垃圾回收)
不可枚举(没有方法能获取所有键)
解决循环拷贝问题
在实现深拷贝时,遇到循环引用(对象属性间接或直接引用自身)会导致无限递归。WeakMap 可以优雅地解决这个问题:
解决方案原理
跟踪已拷贝对象:使用 WeakMap 记录原始对象和其拷贝的对应关系
遇到已拷贝对象直接返回:避免无限递归
自动内存管理:WeakMap 的弱引用特性不会阻止原始对象被垃圾回收
1215

被折叠的 条评论
为什么被折叠?



