在代码编写中,我们经常用到复制/拷贝等操作。JS中存在两种变量类型,分别为值类型和引用类型。
值类型的变量进行拷贝很容易,因为它们的值直接存在内存栈中。但是对于引用类型的变量来说,它们的内存栈中存储的是所占用的数据堆的地址,因此这样 let a = { age: 20} let b = a
简单拷贝,只是将a的地址拷贝给了b,a,b指向了同一个数据堆。这样的拷贝,当b对age进行修改,对a的数据也进行了破坏。
接下来给出两个关于值类型和引用类型的变量拷贝的代码,分析其原理:
// 值类型
let a = 100
let b = a
a = 200
console.log(b) // 100
// 引用类型
let a = { age: 20}
let b = a
b.age = 21
console.log(a.age) // 21
对于值类型的数据,其存储情况如下:
考虑到性能问题,值类型数据的存储和复制都比较快速,不耗性能,因此它被存储到栈里,key下的value直接存储值。
对于上面给出的代码,新建a,并赋值,其存储结果如第一个框所示;新建b,并将a赋值给b,其存储结果如第二个框所示;所以再a变化时,b不受影响,如第三个框所示。
对于引用类型的数据,其存储情况如下:
在栈中,value内只存储key所在的内存地址,通过内存地址到堆里进行查看具体值。
因此,当新建一个引用类型a,再新建一个引用类型b,并将a赋值给b,那b下存储的只是a的内存地址。当b对age进行修改时,堆内的值被修改,也就是a的age也被修改,因为a、b指向同一个内存地址。
所以我们提出了深拷贝。深拷贝主要针对引用类型的变量在进行拷贝时出现只拷贝内存地址而不复制具体值的情况所提出的解决办法。
先给出深拷贝的代码:
/**
* @param {Object} 要拷贝的对象
*/
function deepClone(obj = {}) {
//针对的是对象和数组
//如果obj 不是 对象或数组 或obj传入为空
//typeof可能判断是否是引用类型,而无法进一步判断是哪种引用类型
if(typeof obj != 'object' || obj == null) {
return obj
}
//初始化返回结果
let result
// instanceof 可以判断 数组 类型
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
//保证key不是原型的属性
//递归
result[key] = deepClone(obj[key])
}
}
//返回结果
return result
}
上面的代码需要注意一下3点:
- 注意判断值类型和引用类型,指对引用类型进行深拷贝
- 注意判断是数组还是对象,影响返回结果的初始化
- 递归
给出如下的代码来演示浅拷贝和深拷贝的区别,和深拷贝的作用:
const obj1 = {
age: 20,
name: 'xxx',
address: {
city: 'beijing'
},
arr: ['a', 'b', 'c']
}
const obj2 = obj1
obj2.address.city = 'shanghai'
console.log(obj1.address.city)
const obj3 = deepClone(obj1)
obj3.address.city = 'shenzhen'
console.log(obj1.address.city)
运行,可以在控制器内看到如下结果:
我们创建了一个obj1,其中city的值为beijing,将obj1浅拷贝给obj2,并obj2.address.city = 'shanghai'
,打印obj1.address.city
,发现beijing被修改为shanghai;将obj1深拷贝给obj3,并obj3.address.city = 'shenzhen'
,打印obj1.address.city
,发现没有被修改。这样我们就完成了深拷贝操作。