深拷贝、浅拷贝
- 浅拷贝: 拷贝的是对象的指针,修改内容互相影响。
- 深拷贝: 整个对象拷贝到另一个内存中,修改内容互不影响。
深拷贝实现
常见
不需要借助其他工具的情况下,我们想要实现一个深拷贝的话,最常用的应该是这样的:
JSON.parse(JSON.stringify());
入手
- 拷贝的数据是基础数据类型:无需继续拷贝,直接返回
- 循环引用
- 处理引用类型
- 拷贝对象深度未知:层级递归
* 如果是基础数据类型,直接return
* 如果是引用数据类型,处理Object、Array等
* @param target
*/
const deepClone = <T extends unknown>(target: T) : T =>{
if (typeof target === 'object') {
// 处理数组
if (Array.isArray(target)) {
return target.map(item=> deepClone(item)) as T;
}
// 非 数组,创建新拷贝对象
const cloneTarget : T = {} as T;
for (let _key in target) {
cloneTarget[_key] = deepClone(target[_key])
}
return cloneTarget;
}
return target
}
执行用例
const target = {
test1: 1,
test2: undefined,
test3: {
child: '123',
},
test4: [{
test1: '123',
test2: '123',
test3: [1, 2, 3],
}],
};
const a = target;
// 拷贝前尝试修改值
a.test1 = 666;
const b = deepClone(target);
// 拷贝后修改值
b.test1 = 999;
console.log('拷贝前尝试修改值', a);
console.log('拷贝后修改值', b);
console.log('最终的target', target);
通过控制台打印可以发现,在作出深拷贝前,修改了target的值,然后再通过深拷贝,进而修改值,其最终打印的target只是原来修改的值。
测试用例-循环引用
target.target = target;
const b = deepClone(target);
console.log('拷贝后修改值', b);
好家伙,栓Q了,因为递归进入死循环导致栈内存溢出了,其原因大概就是上面的对象存在循环引用的情况,即对象的属性间接或直接的引用了自身的情况,因此,还需要进一步优化深拷贝代码。
- 解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。
- 这个存储空间,需要可以存储
key-value形式的数据,且key可以是一个引用类型,我们可以选择Map这种数据结构。
/**
* 如果是基础数据类型,直接return
* 如果是引用数据类型,处理Object、Array等
* @param target
*/
// 开辟存储空间
const targetMap = new Map();
const deepClone = <T extends unknown>(target: T): T => {
if (typeof target === 'object') {
// 处理数组
if (Array.isArray(target)) {
return target.map((item) => deepClone(item)) as T;
}
// 非 数组,创建新拷贝对象
const cloneTarget: T = {} as T;
// 有数据,直接返回
if (targetMap.get(target)) {
return targetMap.get(target);
}
// 否则,存储
targetMap.set(target, cloneTarget);
for (const _key in target) {
cloneTarget[_key] = deepClone(target[_key]);
}
return cloneTarget;
}
return target;
};
export { deepClone };
此时可以发现已经不会出现溢出情况。
使用WeakMap
对weakMap(个人理解:弱的Map)和Map有了解过的应该都知道,Map 和 WeakMap 是两种数据结构,可用于操纵键和值之间的关系。
但是,WeakMap 仅接受对象。这意味着我们不能将基本类型用作 WeakMap 的键。与 Map不同,WeakMap 不支持对键和值进行迭代。无法获取 WeakMap 的所有键或值。此外,也没有办法清除 WeakMap。WeakMap 不会阻止在没有对键的引用时对键进行垃圾收集。
Map 可以无限期地维护对键和值的引用。一旦创建了键和值,它们将占用内存,即使没有对它们的引用,也不会被垃圾收集。这可能会导致内存泄漏问题。
由于 WeakMap 保存对键的弱引用,且无法枚举,因此无法使用 keys()、values()、entries() 这些方法。
/**
* 如果是基础数据类型,直接return
* 如果是引用数据类型,处理Object、Array等
* @param target
*/
// 开辟存储空间
const targetMap = new WeakMap();
const deepClone = <T extends unknown>(target: T): T => {
if (typeof target === 'object' && target !== null) {
// 处理数组
if (Array.isArray(target)) {
return target.map((item) => deepClone(item)) as T;
}
// 非 数组,创建新拷贝对象
const cloneTarget: T = {} as T;
// 有数据,直接返回
if (targetMap.get(target)) {
return targetMap.get(target);
}
// 否则,存储
targetMap.set(target, cloneTarget);
for (const _key in target) {
cloneTarget[_key] = deepClone(target[_key]);
}
return cloneTarget;
}
return target;
};
致谢
由于学识有限,如若文中有不足之处,欢迎批评指正!