在 JavaScript 开发中,深拷贝和浅拷贝是处理对象和数组的常见操作。它们涉及到复制数据结构以及处理引用类型数据的方式。
深拷贝和浅拷贝在不同的场景中有各自的应用。浅拷贝适用于简单的数据结构,当我们只需要复制基本数据类型的值或者共享引用类型数据时。而深拷贝则适用于需要完全独立的副本,以避免原始数据的修改对拷贝对象造成影响的情况。
在实际开发中,我们需要根据具体的需求和数据结构选择适当的拷贝方式。对于简单的数据结构,浅拷贝可能足够满足需求,而对于复杂的嵌套对象或数组,深拷贝则更为安全可靠。
了解深拷贝和浅拷贝的概念和区别,可以帮助我们在处理数据时选择合适的拷贝方式,确保数据的正确性和一致性。
浅拷贝是创建一个新的对象或数组,并将原始数据的值复制到新对象中。当原始数据是基本数据类型(如数字、字符串、布尔值)时,浅拷贝会直接复制其值。然而,当原始数据是引用类型(如对象或数组)时,浅拷贝只会复制引用,而不是创建新的独立副本。这意味着新对象和原始对象会共享同一份数据,对其中一个对象的修改会影响到另一个对象。
这里我们创建一个obj对象,并且将obj对象赋值给obj1,当我们改变obj1的name属性和stu属性里的name属性后,同时打印obj和obj1对象,看看有什么区别。
const s = Symbol()
const obj = {
name:'aaa',
age:18,
run:function(){
console.log(111)
},
s:'xxx',
stu:{
name:'bbb',
age:19,
eat:function(){
console.log('eating')
}
}
}
const obj1 = obj
obj1.name = 'bbb'
obj1.stu.name = 'ccc'
console.log(obj1,obj)
我们发现修改obj1后,原来的obj也会被修改,这是怎么回事呢?这里可以画出obj和obj1的内存图。
从图中我们清楚地看出,obj和obj1指向的内存地址是同一个,当其中一个对象修改属性时,所指向它内存地址的对象就会受到影响。
这就是浅拷贝,常见浅拷贝的方式有以下:
深拷贝是创建一个全新的对象或数组,并递归地复制原始数据中的所有值和嵌套对象。深拷贝会创建独立的副本,使得新对象和原始对象完全独立,互不影响。最常见的深拷贝方式使用JSON.parse(JSON.stringify())。我们通过一个例子进行验证一下。
const s = Symbol()
const obj = {
name:'aaa',
age:undefined,
date:new Date(),
run:function(){
console.log(111)
},
s:'xxx',
stu:{
name:'bbb',
age:19,
eat:function(){
console.log('eating')
}
}
}
const obj1 = JSON.parse(JSON.stringify(obj))
obj1.name = 'bbb'
obj1.stu.name = 'ccc'
console.log(obj1,obj)
console.log(obj1.date.getTime())
可以看出,obj1更改的两个属性均不会影响到源对象,因为当我们使用深拷贝的方式的时候,会先创建一份新的内存,两个内存地址不同,自然不会影响到源对象。但是当我们使用JSON.parse(JSON.stringify())克隆对象时,从上图发现以下几点:
所以使用JSON.parse(JSON.stringify())克隆对象需要谨慎考虑,那么现在我们可以封装一个通用方法,实现能够真正深拷贝对象的功能。
深拷贝封装:
function deepClone(obj){
// 判断是否为对象
if(obj !== null && (typeof obj !== "object" || typeof obj !=="function")){
return obj
}
// 判断是否为日期类型
if(obj instanceof Date){
return new Date(obj)
}
// 判断是否为函数,不进行深拷贝
if(obj instanceof Function){
return obj
}
let newObj = Array.isArray(obj) ? [] : {}
for(const key in obj){
newObj[key] = deepClone(obj[key])
}
return newObj
}
封装完测试一下吧!
可以看出基本解决了上述的问题,但是,若一个属性的属性值为当前对象,造成循环引用,我们将obj的stu属性的属性值赋值为obj,会发现“爆栈”的错误。解决问题的方法是使用一个WeakMap来保存拷贝过的对象,检查到当前对象已经拷贝过,直接从WeakMap中获取。(使用WeakMap原因是键必须为对象且当没有对象引用,会自动进行垃圾回收)。
function deepClone(obj, wm = new WeakMap()) {
// 判断是否为对象
if (obj !== null && (typeof obj !== "object" || typeof obj !== "function")) {
return obj
}
// 检查是否已经拷贝过该对象
if (wm.has(obj)) {
return wm.get(obj)
}
// 判断是否为日期类型
if (obj instanceof Date) {
return new Date(obj)
}
// 判断是否为函数,不进行深拷贝
if (obj instanceof Function) {
return obj
}
let newObj = Array.isArray(obj) ? [] : {}
// 将新对象添加到已访问的映射中
wm.set(obj, newObj)
for (const key in obj) {
newObj[key] = deepClone(obj[key], wm)
}
return newObj
}
以上就是深拷贝和浅拷贝的相关知识点,看完这篇文章,你还不懂吗?若有疑问,欢迎评论区留言。