一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情。
日常开发中,免不了面向"CV"编程。JavaScript中有两大数据类型:基本类型、引用类型,不同的数据类型之间复制数据的方式也不太一样。本文重点探讨引用类型的深拷贝。
深拷贝和浅拷贝的区别
深拷贝和浅拷贝最大的区别就在于如何处理引用数据类型。我们都知道,引用数据类型是存在堆中的,引用地址是存在栈中的。对于浅拷贝来说,会把引用数据类型的复制给目标对象,因此浅拷贝引用数据类型,将指向同一个对象。但是深拷贝是新创建一个对象,新创建的对象和源引用数据不是指向同一内存空间,因此深拷贝引用数据类型,不是同一个对象。
如何实现深拷贝呢,
使用JSON.stringfy()和parse
使用JSON的序列化和反序列化可以实现简单引用数据类型的深拷贝,实现过程就是先JSON.stringify()将引用数据转换为字符串,然后使用JSON.parse()将JSON字符串解析为对象,在此过程中涉及新创建对象,因此深拷贝前后不是同一个对象。
刚才也说了,使用JSON序列化/反序列化只能适用于简单的引用数据类型,以下情况就不适用:
- 源对象中存在函数
- 源对象存在循环引用
- 源对象中存在Map、Set数据类型
以上数据类型,在使用JSON.stringify()时,会忽略转换,因此解析时就会存在数据丢失。
举个栗子
let obj = {fn: () => {}, x:1};
JSON.stringify(obj); // "{"x": 1}",函数会被忽略
obj.z = new Set([1,2,3]);
JSON.stringify(obj); // "{"x": 1, "z": {}}", // 无法转换Set、Map,因为Set和Map的属性不可枚举
obj.y = obj;
JSON.stringify(obj); // 报错,无法转换循环引用
使用Object.assign()
Object.assign()用于将一个或多个源对象的所有可枚举属性复制到目标对象中,并且返回目标对象,注意此方法不是深拷贝。原因就在于它是复制对象的属性,并没有创建新的对象。
举个栗子
let a1 = {a: 1, b: 2};
let a2 = {b: 3, c: 4};
let a3 = Object.assign(a1, a2);
a1.a = 0;
console.log(a1, a3); // {a:0,b:3,c:4},{a:0,b:3,c:4},由此可见并不是深拷贝
深拷贝
手写深拷贝,需要考虑以下情况:
- 处理Object、Array、Map、Set等数据类型
- 处理循环引用
- 处理函数
实现代码:
/**
* 深拷贝
* @param {*} obj 任意对象
* @param {*} map 避免循环引用,使用WeakMap既保留了Map的get/set,同时也可以避免内存泄漏
*/
export const deepClone = function (obj, map = new WeakMap()) {
// 判断null时采用非严格判断,包含null和undefined的情况
if (obj == null || typeof obj !== 'object') {
return obj; // 非引用类型或者是null、undefined,就直接返回
}
// 处理循环引用
let objMap = map.get(obj);
// 如果存在循环引用,则直接返回
if (objMap) return objMap;
let result = {};
map.set(obj, result);
// 处理Map类型
// 细分类型的判断,使用instanceof
if (obj instanceof Map) {
result = new Map(); // 创建一个新的Map
obj.forEach((v, k) => {
let v1 = deepClone(v, map); // 递归调用
let k1 = deepClone(k, map); // Map的key,可能是引用数据类型,因此需要递归调用
result.set(k1, v1);
})
}
// 处理Set类型
if (obj instanceof Set) {
result = new Set();
obj.forEach(v => {
let v1 = deepClone(v, map);
result.add(v1);
})
}
// 处理Array数组
if (obj instanceof Array) {
// 调用数组的map遍历并返回新的数组
result = obj.map(item => deepClone(item, map));
}
// 处理Object
for(let k in obj) {
// Object的key是基本数据类型,不存在深拷贝
let v1 = deepClone(obj[k], map);
result[k] = v1;
}
return result;
}
测试一下代码效果:
// 测试
let obj = {
set: new Set([1,2,3]),
map: new Map([['x', 1], ['y', 2]]),
object: {
z: 1
},
arr: [1,2,3],
fn: () => {
console.log('function');
}
}
obj.self = obj;
let res = deepClone(obj);
obj.object.z = 3;
console.log(res.object.z); // 3
总结
- 深拷贝是新创建一个对象,原对象和目标对象在内存中是不同的值,彼此修改互补影响。
- 手写实现深拷贝,需要考虑函数、循环引用、Map、Set等数据类型
- 实现深拷贝,需要充分利用递归函数
原创不易,转载请注明出处。