js中的浅拷贝和深拷贝
在讨论js的浅拷贝之前,我们先来看一下js的数据类型。
一、js的数据类型
- 基本数据类型:存放在栈里面,里面直接开辟一个空间,存放的是值。栈是有结构的,每个区块都是按照后进先出的方式次序存放,基本类型的数据相对是比较稳定的,占的内存也比较小,所有寻找速度比较快,如果基本类型复制的话,栈中重新开辟个新的内存空间来储存新复制的新值,所以两者是独立的,没有任何关联!
- 引用类型:存放在堆里面,首先在栈里面存放地址(十六进制表示),地址在指向堆里面的实例,真正的对象实例存放在堆空间中。堆是没有结构的,数据可以任意存放,我们常常实际操作的是引用而不是操作实际的对象!你可以这么理解,栈中保存着一个地址,这个地址指向堆中的对象,如果复制的是引用类型,那么复制的是栈内存中的引用地址,两个引用地址指向的还是同一个对象!
二、什么是深拷贝和浅拷贝?
深拷贝和浅拷贝只针对引用数据类型,根本区别在于是否是真正获取了一个对象的复制实体,而不是引用。
简单来说,假设我们有两个对象A和B,把A复制给B,当我们修改B里面的值时,如果A发生了改变,那么就是浅拷贝,A没有改变则是深拷贝。
三、实现浅拷贝
直接赋值
var arr1 = [1,2,3]; var arr2 ; arr2 = arr1; arr2[0] = 9; console.log(arr1);//[9,2,3] console.log(arr2);//[9,2,3]
可以看出我们只是对arr2的值进行了改变,但是输出发现arr1的值也发生了改变。实际上当我们把arr1复制给arr2时,只是把指向[1,2,3]这个数值的地址赋给了arr2,就相当于arr1和arr2都指向了这个数值,当我们通过arr2改变这个数值时,再通过arr1去拿也就是改变后的值。
四、实现深拷贝
1、采用递归去拷贝所有层级属性
function deepClone(obj){ //Array.isArray() 用于确定传递的值是否是一个 Array。 let objClone = Array.isArray(obj)?[]:{}; if(obj && typeof obj==="object"){ for(key in obj){ //hasOwnProperty()方法来忽略继承属性。 if(obj.hasOwnProperty(key)){ //判断ojb子元素是否为对象,如果是,递归复制 if(obj[key]&&typeof obj[key] ==="object"){ objClone[key] = deepClone(obj[key]); }else{ //如果不是,简单复制 objClone[key] = obj[key]; } } } } return objClone; } let a=[1,2,3,4], b=deepClone(a); a[0]=2; console.log(a,b);
2、JSON对象的parse和stringify
function deepClone(obj){ let _obj = JSON.stringify(obj), objClone = JSON.parse(_obj); return objClone } let a=[0,1,[2,3],4], b=deepClone(a); a[0]=1; a[2][0]=1; console.log(a,b);
缺点:无法实现对对象中方法的深拷贝。
3、jQuery的extend方法实现深拷贝
var array = [1,2,3,4]; var newArray = $.extend(true,[],array); // true为深拷贝,false为浅拷贝
4、假的深拷贝slice方法
let a=[1,2,3,4], b=a.slice(); a[0]=2; console.log(a,b);
是不是意味slice方法也可以进行深拷贝了?那我们把a改改再来看一下
let a=[0,1,[2,3],4], b=a.slice(); a[0]=1; a[2][0]=1; console.log(a,b);
可以看到拷贝的不彻底,一级属性像上面一样不受影响,但是二级属性明显没有深拷贝成功,所以说明slice不是真正的深拷贝。同理,concat方法与slice也存在这样的情况,他们都不是真正的深拷贝,这里需要注意。