以下是使用递归+循环方式手写功能全面的深度克隆函数,包含原型链指向,排除原型上的属性以及考虑环形引用的情况,采取缓存措施处理有可能导致的内存泄漏的情况。
// 手写深度克隆函数
// 缓存一下被克隆过的对象,防止环形引用造成无限递归
// 使用WeakMap,比Map更好,防止对象使用后由于Map中有引用而不会被回收,造成内存泄漏
const cache = new WeakMap();
function deepClone(value) {
// 如果是原始类型,直接返回
if (typeof value !== 'object' || value === null) {
return value;
}
// 先从缓存取一下原对象是否已经被克隆过,有值直接返回
const cached = cache.get(value);
if (cached) {
return cached;
}
// 引用类型,创建空数组/对象接受克隆后的值
const result = Array.isArray(value) ? [] : {};
// 将原对象和克隆的对象设置到缓存中
cache.set(value, result);
// 设置一下原型链,让克隆出来的对象的原型与原对象原型保持一致
Object.setPrototypeOf(result, Object.getPrototypeOf(value))
// 循环原对象每个key,赋值到新对象中
for (const key in value) {
// 排除prototype原型上的属性,不克隆原型上的属性
if (value.hasOwnProperty(key)) {
// 针对原对象中每个值递归再深度克隆一次
result[key] = deepClone(value[key])
}
}
return result
}
// 测试一下
function Test(x, y) {
this.x = x;
this.y = y;
}
Test.prototype.z = 'z'
var p = new Test(1, 2);
p.a = [1,2,3]
p.c = p // 环形引用
const newP = deepClone(p)
console.log('origin p: ', p) // Test { x: 1, y: 2, a: [ 1, 2, 3 ], c: [Circular] }
console.log('new p: ', newP) // Test { x: 1, y: 2, a: [ 1, 2, 3 ], c: [Circular] }
console.log(p.prototype === newP.prototype) // true
console.log(p.a === newP.a) // false