一、了解概念:
对象拷贝,简而言之就是将对象再复制一份,但是,复制的方法不同将会得到不同的结果。比如直接定义一个新变量赋值为一个对象:
let obj = {
name: "张三",
like: "sing",
};
// 2. 直接将对象赋值给变量 newObj
let newObj = obj;
// 3.修改obj的like属性
obj.like = "eat";
// 4.输出 newObj
console.log(newObj);
控制台输出:
小结:我们可以看到,我修改的是obj对象的值,但是newObj里面的值也修改了,这就是浅拷贝,说专业点就是,当创建 obj 对象时,它在堆内存中开辟了一块空间存储对象的内容,而当 newobj 直接赋值为 obj 时,newobj 并不会再重新开辟一块堆内存,而是和obj共用一个地址,这个地址存在栈内存中,所以它俩谁进行修改,都会一起进行改变,请看下图:
然而这并不是我们想看到的,我们需要的是修改一个,另外一个不受影响,那么如何实现呢?
二、常用方法:
(一)展开运算符:展开运算符使用的对象如果只是针对简单的一级基础数据,就是深拷贝; 展开运算符使用的对象内容包含二级或更多的复杂的数据,那就是浅拷贝,请看例子
1、一级基础数据(没有对象包数组等情况)
let obj = {
name: "张三",
like: "sing",
};
let obj2 = { ...obj };
obj2.name = '李四';
console.log(obj.name);
控制台输出:
小结:通过运算符的方法,修改obj2里的属性值,并没有影响到obj本身,所以算是深拷贝成功了,当然这只是没有复杂嵌套的关系。
2、二级或更多的数据
name: "张三",
like: "sing",
arr:[1,2,3]
};
let obj1 = {...obj}
// 向数组里添加一个元素
obj.arr.push(4)
console.log(obj,obj1);
控制台输出:
小结:当有二级数据时,展开运算符的方法就不那么管用了,当然这里也可以继续使用展开运算符,不过嵌套的数据越复杂时,可能有些得不偿失,所以碰到此类情况,不做考虑。
(二)Object.assign()方法,主要用于对象合并,将源对象中的属性复制到目标对象中。
let obj = { name: '张三' };
let obj1 = Object.assign({}, obj);
obj1.name = '狂徒';
console.log(obj,obj1);
控制台输出:
小结:可以看到通过Object.assign()方法也可以完成深拷贝。
(三)数组api——concat()与slice()方法,常用于拷贝数组。
1、数组.concat()
let arr = [1, 2];
let arr1 = arr.concat();
arr.push(3);
console.log('这是arr',arr);
console.log('这是arr1',arr1)
控制台输出:
2、数组.slice()
let arr = [1, 2];
let arr1 = arr.slice();
arr.push(3);
console.log('这是arr',arr);
console.log('这是arr1',arr1)
控制台输出:
小结:可以看到两种数组api实现的方式都是深拷贝。
---------------------------------------------- 分割线 --------------------------------------------------
看到这里,相信有小伙伴会问了,如果只拷贝一层的话,这些方法其实都可以完成深拷贝,但是数据如果出现多层嵌套呢?我们还能实现深拷贝吗?如何进行深拷贝?
说一下深拷贝,它不会像浅拷贝那样只拷贝一层,而是有多少层我就拷贝多少层,要真正的做到全部内容都放在自己新开辟的内存里。可以利用递归思想实现深拷贝,看例子。
(1)使用JSON.parse(JSON.stringify())完成深拷贝
let obj = { name: '张三', hobby: ['打豆豆'] };
let obj1 = JSON.parse(JSON.stringify(obj));
obj.hobby.push('吃饭');
console.log('这是obj',obj,'这是obj1',obj1);
控制台输出:
小结:JSON.stringify将对象先转成字符串,再通过JSON.parse将字符串转成对象,此时对象中每个层级的堆内存都是新开辟的。
(2)使用递归
// 1.定义一个嵌套关系的对象
let obj = {
name:'张三',
age:18,
hobby:{
a:'吃饭',
b:'睡觉',
c:'打豆豆'
}
}
// 2.封装函数
function newObj(obj) {
let Obj1 = {}
// 3.使用for in 遍历
for(let key in obj){
// 判断有无二级关系
if(typeof obj[key] == "object" && obj[key] != 'null'){
// 4.使用递归
Obj1[key] = newObj(obj[key])
} else {
// 这是是没有嵌套的情况下
Obj1[key] = obj[key]
}
}
return Obj1
}
// 5.调函数得到新对象
let Obj1 = newObj(obj)
// 6.修改值,控制台看变化
Obj1.hobby.a = '敲代码'
console.log('这是obj',obj,'这是Obj1',Obj1);
控制台输出:
小结:拷贝成功,可以看到hobby里a的值发生变化,而原对象并没有改变。
三、总结
浅拷贝能用则用,不能用就使用深拷贝!~