一、如何区分浅拷贝和深拷贝
简单来说,如果b复制了a,当修改a时,看b是否会发生变化,如果b也变了,说明这是浅拷贝,如果b没变,说明这是深拷贝。
先看👀个浅拷贝的例子:
![](https://img-blog.csdnimg.cn/img_convert/46a42c9b1186be071c1f501af9aae1d6.jpeg)
明明b复制了a,修改数组a,为什么数组b也会跟着变呢?这就得引入基本数据类型和引用数据类型的概念了。
二、基本数据类型与引用数据类型
基本数据类型有:number、string、boolean、null、undefined、symbol以及未来ES10新增的BigInt(任意精度整数)七类。
引用数据类型有:对象、数组、函数等。
这两类的数据存储分别是这样的:
基本数据类型--名和值存储在栈内存中
举例:
![](https://img-blog.csdnimg.cn/img_convert/d85910d03d481dc91d2f96cf6d480d86.jpeg)
let a=1;
![](https://img-blog.csdnimg.cn/img_convert/1b36c25421eaf39141e3874d4a04fc5f.jpeg)
当设置b=a时,栈内存会开辟一个新内存:
![](https://img-blog.csdnimg.cn/img_convert/c4b356c410d498028c35b52971aae1ec.jpeg)
当设置a=2时,对b并没有影响。
虽然这里b不受a的影响,这也算不上是深拷贝,因为深拷贝本身只针对较为复杂的object类型数据。
引用数据类型--名存在栈内存中,值存在堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值。
举例:
![](https://img-blog.csdnimg.cn/img_convert/46a42c9b1186be071c1f501af9aae1d6.jpeg)
let a = ["a", "b", "c", "d"]
![](https://img-blog.csdnimg.cn/img_convert/e6fbf2661e00be9b88b97b8fb43f389f.jpeg)
当设置b=a进行拷贝时,其实复制的是a的引用地址,而不是堆内存里面的值。
![](https://img-blog.csdnimg.cn/img_convert/c8c56531c3a119f6479dc6ca593044a1.jpeg)
当设置a[0]='test'时,由于a与b指向的是同一个地址,所以b也受了影响,这就是所谓的浅拷贝。
![](https://img-blog.csdnimg.cn/img_convert/0b9fc660e00a84e7f24883b5e167233e.jpeg)
如果在堆内存中也开辟一个新的内存给b存放值,就像基本数据类型那样,岂不就是能达到深拷贝的效果了。例如下面这样:
![](https://img-blog.csdnimg.cn/img_convert/cfc7a032f59f044825ed08db8dac350e.jpeg)
三、如何实现深拷贝
封装一个简单的深拷贝函数,用递归去复制所有层级属性。
逐层递归的代码示例:
const copyObj = (obj = {}) => {
//变量先置空
let newObj = null;
//判断是否需要继续进行递归
if (typeof (obj) == 'object' && obj !== null) {
newObj = obj instanceof Array ? [] : {};
//进行下一层递归克隆
for (let i in obj) {
newObj[i] = copyObj(obj[i]);
}
} else {
//如果不是对象直接赋值
newObj = obj;
};
return newObj;
};
let obj = {
numberParam: 1,
functionParam: () => {
console.log('测试属性值是函数');
}
};
const newObj = copyObj(obj);
obj.numberParam = 10;
console.log("obj=>",obj);
console.log("newObj=>",newObj);
![](https://img-blog.csdnimg.cn/img_convert/07356fa335307460434ee64367aa9f4e.jpeg)
借用JSON对象的JSON.stringify()和JSON.parse()
代码示例:
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
let a = {
b: {
c: 1
}
};
let b = deepClone(a);
a.b.c = "test";
console.log("a=>", a);
console.log("b=>", b);
![](https://img-blog.csdnimg.cn/img_convert/03b8307970f18ff13ae0dd8279d7b70e.jpeg)
⚠️当对象内容项为number,string,boolean时,没有什么问题。但是,当对象内容项为undefined,null,Date,RegExp,function,error时,使用JSON.stringify()进行拷贝就会出问题。
3、使用第三方库lodash中的cloneDeep()方法
如果项目中只需要一个深拷贝的功能,这种情况下为了一个功能引入整个第三方库就显得很不值得了。不如写一个递归函数对于项目来说性能更好。
lodash.cloneDeep()代码示例:
import lodash from 'lodash';
let obj = {
a: {
c: 1,
d: [1, 3, 5]
},
b: 2
};
const newObj = lodash.cloneDeep(obj);
obj.b = 20;
console.log(newObj.b);//输出2
4、jquery的extend()方法(推荐在JQ中使用)
这个方法仅适用于JQuery的项目。JQuery自身携带的extend()方法可以进行深拷贝,不用自己写递归也不用引入第三方库。
代码示例:
let obj = {
a: {
c: 1,
d: [1, 3, 5],
},
b: 2
}
let newObj = $.extend(true, {}, obj);
obj.b = 20;
console.log(newObj.b); //输出2
如果$.extend()没有传true不能深拷贝到第二层:
let obj1 = {
a: {
d: [1, 2, 3]
}
}
// $.extend()没有传true
let newObj1 = $.extend({}, obj1);
obj1.a.d[2] = 20;
console.log("没有true-->", newObj1);
let obj2 = {
a: {
d: [1, 2, 3],
}
}
// $.extend()传true
let newObj2 = $.extend(true, {}, obj2);
obj2.a.d[2] = 20;
console.log("有true-->", newObj2);
![](https://img-blog.csdnimg.cn/img_convert/f38db1d8e88bfd0ad1cb65bea27cd16d.jpeg)
5、JS原生的深拷贝:structuredClone()
const obj = {
id: 'abcd1234',
values: ['a', 'b']
};
const clone2 = structuredClone(obj);
clone2.values.push('x');
console.log("obj->", obj);
console.log("clone2->", clone2);
![](https://img-blog.csdnimg.cn/img_convert/4b815b6fac50fe15c64b07dfca39c466.jpeg)
四、总结:
1、进行深拷贝的方法
递归函数 (推荐使用,项目中使用的更多,更小,更安全)
JSON.stringify() 和JSON.parse() ; (不推荐使用,如果遇到Function,Date等类型的变量容易出现一些意料之外的问题)
第三方库lodash的cloneDeep()方法 (就情况而定,如果项目中原先就有lodash这个第三方库,可以使用,否则还是推荐使用递归函数,不然成本太高。)
JQuery的extend()函数 (推荐在JQuery项目中使用,其他项目依然推荐是用递归函数)
structuredClone() 方法是浏览器原生支持的,可拷贝无限嵌套的对象和数组,但对于 Function 类型会报错,但最主要的缺点是兼容性问题。
2、注意⚠️
(1)数组的concat()方法和slice()方法都不是真正的深拷贝
数组的concat()方法和slice()方法,拷贝的不彻底,第一层的属性确实是深拷贝,拥有了独立的内存,但更深的属性仍然公用了地址,说明数组的concat()方法和slice()方法都不是真正的深拷贝。
(2)ES6的扩展运算符执行的是浅拷贝
const obj = {
id: 'abcd1234',
values: ['a', 'b']
};
const clone1 = { ...obj };
obj.id = "123";
obj.values[0] = "c";
console.log("obj->", obj);
console.log("clone1->", clone1);
![](https://img-blog.csdnimg.cn/img_convert/15efbc5f6ce14801f6472173e6717632.jpeg)