首先要知道JS的数据类型分为基本数据类型(String、Number、Boolean 等)和引用数据类型(Object、Array、Function)。基本数据类型值的存储是直接存储在栈内存中的,而引用数据类型值的储存是不同的,值是存储在堆内存中,不是直接存储在栈内存中,栈内存存储的是值的引用地址,可以通过引用地址找到堆内存中所对应的值。看图可以更好的理解。
现在有a对象,然后变量b等于a。
let a = { name : 'lisi' }
let b = a
他们的值在内存中是这样存储的。
有了对基本数据类型和引用数据在内存的储存方法,就可以继续了。
一、直接赋值
如果是基本数据类型的直接赋值,得到的是一个新的值,会在栈内存中新开辟一个空间储存赋值的值,所以不用考虑浅拷贝和深拷贝。
而引用数据类型的直接赋值,得到的是一个地址的引用,就会出现下面的情况。
let obj1 = {
name: 'zhangsan',
age: 18
}
let obj2 = obj1
obj2.name = 'lisi'
console.log('obj1:',obj1);
console.log('obj2:',obj2);
原因就是直接赋值后,obj2得到的只是obj1值的引用地址,此时obj1和obj2引用地址一样,指向堆内存的值也是同一个。所以obj2修改name属性后,obj1对应的值也改变了,因为是同一个值。
二、浅拷贝
浅拷贝就是为了解决上面的情况,让赋值后的对象拿到的是新的引用地址和新的值,一人一份,不用两个对象用同一份值。但是都叫浅拷贝了,意思就是没有完全一人一份,有的地方还是会共用同一份值。比如一个对象的一个属性值是数组或对象(相当于对象里还有一个对象),而浅拷贝后,拷贝到的这个属性得到的还是同一个引用地址。
浅拷贝只能拷贝一层的基本数据类型,多层后就无法拷贝到了,如下例子。(我使用了lodash库中的 _clone方法实现浅拷贝,使用前记得引入,而ES6中的Object.assign也可以进行浅拷贝。)
let obj1 = {
name: 'zhangsan',
age: 18,
friend: {
name: 'xiaobai'
}
}
let obj2 = _.clone(obj1) //进行浅拷贝
obj2.name = 'lisi' //修改 obj2.name 看看obj1.name 是否改变
obj2.friend.name = 'afu' //修改 obj2.friend.name 看看 obj1里的是否一起改变
console.log('obj1:', obj1);
console.log('obj2:', obj2);
可以看出来浅拷贝解决了直接赋值产生的问题,但是对于引用数据类型里有其他引用类型数据类型的时候,并不能完全的拷贝,相当于只能拷贝一层,而深拷贝就可以解决这个问题
三、深拷贝
深拷贝就是可以给你一份一模一样的值,会在堆内存中开辟一个新的区域存放新的值,新的值和原值不存在共享内存的情况,不论你这个引用数据类型里嵌套了多少层,都可以拷贝给你,实现真正的拷贝。直接看例子:(我使用lodash库的_.cloneDeep方法实现深拷贝)
let obj1 = {
name: 'zhangsan',
age: 18,
friend: {
name: 'xiaobai',
animal: ['dog'] //数组也是引用类型
}
}
let obj2 = _.cloneDeep(obj1) //进行深拷贝
obj2.friend.animal[0] = 'cat' //修改了第三层的数据
console.log('obj1:', obj1);
console.log('obj2:', obj2);
可以发现修改了obj2的值,obj1的中的值没有跟着变化,表示这两个对象是完全不同的了,这就是深拷贝的作用。