什么叫深拷贝?
深拷贝:深拷贝是指,拷贝对象的具体内容,二内存地址是自主分配的,拷贝结束之后俩个对象虽然存的值是一样的,但是内存地址不一样,俩个对象页互相不影响,互不干涉
实现深拷贝的几种方法?
JSON深拷贝
JSON.parse(JSON.stringify)为什么可以实现深拷贝?
使用JSON.stringify将对象序列化,转成字符串后,存储在硬盘上,在通过JSON.parse()反序列化,将字符串转成对象.它有一个存储操作就是深拷贝.
let userA = {
name: "张三",
age: 24,
num: undefined,
sex: () => {
console.log("我是函数")
},
time: new Date()
}
let userB = JSON.parse(JSON.stringify(userA));
userB.age = 30;
console.log("对象userA", userA);
console.log("对象userB", userB);
最终结果:
以上的方式:我们可以仔细看一下,它在user A中有5个对象,但是B中只有3个,所以我们可以看出它在拷贝的过程中把某些对象丢了.我们再仔细看一下,它在拷贝过程中,将undefined和函数弄丢了,将time做了一个转义.但是这不是我们想要的,所以它存在以下缺点:
- 忽略undefined和symbol
- 无法拷贝function
- 无法拷贝循环引用对象(比如对象属性的值就是它自己)
- 对Date、RegExp等一些内置函数的处理会出现问题,比如时间不再是对象格式。
递归实现
递归实现的原理很简单,就是循环遍历我们的对象,如果遇到属性是对象的,再一次递归循环遍历即可。
let userA = {
name: "张三",
age: 20,
sex: () => {
console.log("小猪课堂");
},
arr: ["1", "231", "342242"],
obj: {
like: "打游戏"
},
likesFood: new Set(["fish", "banana"]),
date: new Date()
};
function deepClone(obj) {
// 如果传入的类型不对,则不做处理
if (typeof obj !== "object" || obj === null) {
return;
}
let newObj = {}; // 新对象
const dataKeys = Object.keys(obj); // 获取原对象所有键值
dataKeys.forEach((value) => {
const currentValue = obj[value];
// 基础类型直接赋值
if (typeof currentValue !== "object" || currentValue === null) {
newObj[value] = currentValue;
} else if (Array.isArray(currentValue)) {
// 实现数组的深拷贝
newObj[value] = [...currentValue];
} else if (currentValue instanceof Set) {
// 实现set数据的深拷贝
newObj[value] = new Set([...currentValue]);
} else if (currentValue instanceof Map) {
// 实现map数据的深拷贝
newObj[value] = new Map([...currentValue]);
} else if (currentValue instanceof Date) {
// 日期类型深拷贝
newObj[value] = new Date(currentValue.valueOf())
} else {
// 普通对象则递归赋值
newObj[value] = deepClone(currentValue);
}
});
return newObj;
}
let userB = deepClone(userA);
userB.obj.like = "睡觉"
console.log("userA",userA)
console.log("userB",userB)
虽然递归能够满足我们日常中大部分列子,很多小伙伴用的就是这种方法,但是它也有问题所在,比如循环引用的问题,对象的属性值就是该对象。
如果将上述代码改成:
let userA = {
name: "张三",
age: 20,
sex: () => {
console.log("小猪课堂");
},
arr: ["1", "231", "342242"],
obj: {
like: "打游戏"
},
likesFood: new Set(["fish", "banana"]),
date: new Date()
};
userA.name = userA;
我们就加了一行代码,将name的值改为了userA,这就产生了循环引用/此时我们在使用该递归进行深拷贝,直接报栈溢出了,进入了死循环!
所以要接着进行升级:
let userA = {
name: "张三",
age: 20,
sex: () => {
console.log("小猪课堂");
},
arr: ["1", "231", "342242"],
obj: {
like: "打游戏"
},
likesFood: new Set(["fish", "banana"]),
date: new Date()
};
userA.name = userA;
function deepClone(obj, hashMap = new WeakMap()) {
// 如果传入的类型不对,则不做处理
if (typeof obj !== "object" || obj === null) {
return;
}
// 查缓存字典中是否已有需要克隆的对象,有的话直接返回同一个对象(同一个引用,不用递归无限创建进而导致栈溢出了)
if (hashMap.has(obj)) {
return hashMap.get(obj);
}
let newObj = {}; // 新对象
const dataKeys = Object.keys(obj); // 获取原对象所有键值
dataKeys.forEach((value) => {
const currentValue = obj[value];
// 基础类型直接赋值
if (typeof currentValue !== "object" || currentValue === null) {
newObj[value] = currentValue;
} else if (Array.isArray(currentValue)) {
// 实现数组的深拷贝
newObj[value] = [...currentValue];
} else if (currentValue instanceof Set) {
// 实现set数据的深拷贝
newObj[value] = new Set([...currentValue]);
} else if (currentValue instanceof Map) {
// 实现map数据的深拷贝
newObj[value] = new Map([...currentValue]);
} else if (currentValue instanceof Date) {
// 日期类型深拷贝
newObj[value] = new Date(currentValue.valueOf())
} else {
hashMap.set(obj, newObj); // 哈希表缓存新值
// 普通对象则递归赋值
newObj[value] = deepClone(currentValue,hashMap);
}
});
return newObj;
}
let userB = deepClone(userA);
userB.obj.like = "睡觉"
console.log("userA", userA)
console.log("userB", userB)
上段代码解决了我们循环引用的问题,主要利用了WeakMap()这个方法,它的作用其实主要就是创建一个缓存表,我们深拷贝时,会先判断该属性值是否在缓存表当中,存在则说明是循环引用。
Object.assign()
Object.assign()是一种比较简单的深拷贝方法,其实说它是深拷贝,一点都不严谨,因为它只会拷贝第一层,也就是当对象内嵌套有对象的时候,被嵌套的对象进行的还是浅拷贝。
let userA = {
name: "张三",
age: 24
}
let userB = Object.assign({},userA);
userB.age = 30;
console.log("userA",userA);
console.log("userB",userB);
利用Lodash库(完美)
这个库相信很多小伙伴都用过,简直是前端开发利器。lodash是一套工具库,内部封装了很多字符串、数组、对象等常见数据类型的处理函数。
它提供了给我们进行深拷贝的方法,各种情况他都考虑到了,如果不在乎引入第三方库的话,强烈建议使用此方法。
直接调用提供给我们的_.cloneDeep方法即可。
_.cloneDeep(obj1);
利用Jquery
Jquery提供了深拷贝的方法给我们,但是目前这个大前端环境下,很多人不在使用jquery,所以这儿就简单介绍一下。
$.extend(true,{},obj)
直接调用extend方法即可。