跳到主要内容

深度拷贝技术点

WeakMap

我使用 WeakMap 来跟踪已拷贝的对象,这有助于解决循环引用的问题。与普通的 Map 不同,WeakMap 不会阻止其键被垃圾回收,这有助于防止内存泄漏。

Object.prototype.toString.call()

为了获取对象的确切类型,我使用 Object.prototype.toString.call()。这是一个常见的技巧,可以准确获取对象的内部 [[Class]] 属性,确保类型判断的准确性。

Function

在复制函数时,我通过创建一个新的函数,该函数调用原始函数并传递所有参数来实现。这种方法确保了函数的行为与原始函数一致。

Spread Operator (...)

我使用扩展运算符 ... 从对象中提取所有自身的属性名和符号。这种方式简洁且高效,适用于浅拷贝场景。

Object.getOwnPropertyNames() 和 Object.getOwnPropertySymbols()

为了获取对象的所有自有属性名和符号,包括不可枚举的属性,我使用 Object.getOwnPropertyNames()Object.getOwnPropertySymbols()。这确保了对象的所有属性都被正确复制。

Object.getOwnPropertyDescriptor() 和 Object.defineProperty()

我通过 Object.getOwnPropertyDescriptor() 获取对象属性的描述符,并使用 Object.defineProperty() 在新对象上定义属性。这确保了属性的所有特性(如值、可枚举性、配置性和可写性)都被正确地复制。

Set 和 Map

对于 SetMap 的实例,我迭代其所有值或键值对,并递归地进行深拷贝。这种处理方式确保了集合中的每一个元素都被正确复制。

Array

数组的处理相对简单,我创建一个新的空数组,后续的值复制会在 _cloneProperties 函数中进行。这确保了数组的结构和内容都被正确复制。

Prototypes 和 Object.create()

我使用 Object.create(Object.getPrototypeOf(currentObj)) 来创建一个新对象,该对象与原始对象共享相同的原型。这确保了新对象继承了原型链上的属性和方法。

Date, RegExp, 和 Error

这些对象类型有特殊的构造函数,我利用它们的构造函数来复制它们的值。例如,使用 new Date(originalDate) 来复制日期对象,确保其内部状态保持一致。

function deepClone(obj) {
const visited = new WeakMap();

function createClonedObject(currentObj) {
const type = Object.prototype.toString.call(currentObj);
switch (type) {
case '[object Array]':
return [];
case '[object Date]':
return new Date(currentObj);
case '[object RegExp]':
return new RegExp(currentObj);
case '[object Set]':
return new Set();
case '[object Map]':
return new Map();
case '[object Function]':
return function () {
return currentObj.apply(this, arguments);
};
case '[object Error]':
return new Error(currentObj.message);
default:
return Object.create(Object.getPrototypeOf(currentObj));
}
}

function cloneRecursive(currentObj) {
if (currentObj === null || typeof currentObj !== 'object') return currentObj;

if (visited.has(currentObj)) return visited.get(currentObj);

const clonedObj = createClonedObject(currentObj);

visited.set(currentObj, clonedObj);

if (currentObj instanceof Set) {
for (let value of currentObj) {
clonedObj.add(cloneRecursive(value));
}
} else if (currentObj instanceof Map) {
for (let [key, value] of currentObj) {
clonedObj.set(cloneRecursive(key), cloneRecursive(value));
}
} else {
copyProperties(currentObj, clonedObj);
}

return clonedObj;
}

function copyProperties(source, target) {
const allKeys = [...Object.getOwnPropertyNames(source), ...Object.getOwnPropertySymbols(source)];
for (let key of allKeys) {
let descriptor = Object.getOwnPropertyDescriptor(source, key);
if (descriptor.enumerable && !descriptor.get && !descriptor.set) {
target[key] = cloneRecursive(source[key]);
} else {
if (descriptor.value) descriptor.value = cloneRecursive(descriptor.value);
Object.defineProperty(target, key, descriptor);
}
}
}

return cloneRecursive(obj);
}

// 使用示例
const originalObject = {
number: 123,
string: 'Hello World',
bool: true,
nullValue: null,
undefinedValue: undefined,
date: new Date(),
regexp: /test/g,
array: [1, 2, 3],
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
symbol: Symbol('test'),
nestedObject: {
number: 456,
string: 'Nested Hello World',
},
func: function () {
return "I'm a function";
},
arrowFunction: () => "I'm an arrow function",
};

// 创建循环引用
originalObject.self = originalObject;
originalObject.nestedObject.parent = originalObject;

const clonedObject = deepClone(originalObject);

// 一些测试断言
console.log(clonedObject !== originalObject); // true
console.log(clonedObject.self !== originalObject.self); // true
console.log(clonedObject.nestedObject.parent !== originalObject.nestedObject.parent); // true
console.log(clonedObject.array !== originalObject.array); // true
console.log(clonedObject.func() === originalObject.func()); // true
console.log(clonedObject.arrowFunction() === originalObject.arrowFunction()); // true
console.log(clonedObject.date.getTime() === originalObject.date.getTime()); // true
console.log(clonedObject.regexp.source === originalObject.regexp.source); // true