如今面向对象的理念已经深入到猿界的千家万户了,JavaScript当然也在其中的行列当中,而且特点就是面向对象。
在JS中我们常常会遇到两种数据类型,一种就是基本数据类型,另一种则是引用类型,我们也常常把它叫做复合类型。基本数据类型存储在栈内存中(存储基本类型的变量和对象的引用变量),而引用数据类型存在堆内存中(存储由new创建的对象)。
对于简单的变量如果进行复制,则不会发生引用。
var mickey="abc";
var copyMickey=mickey;
var mickey="defg"
console.log(mickey); //defg
console.log(copyMickey) //abc
但是对于对象而言,它对内存的消耗还是相当大的,如果按照赋值的方式将一个对象赋值给另一个对象,那么我们得到的新对象其实不是一个独立的个体,也就是说得到的新对象指向的还是原来的对象,在内存中可以解释为两个对象都指向同一个内存空间,如果改变其中某个对象的一个属性,那么另一个对象相应的属性也会随之改变。因此这就诱发了引用。
引用只发生在对象层面,如下:
var a={name:'wangwu',arr:[1,3,4,2]};
var b=a;
b.sex='男';
console.log(a); //{name:'wangwu',arr:[1,3,4,2],sex:'男'}
console.log(b); //{name:'wangwu',arr:[1,3,4,2],sex:'男'}
下面进入正题,如何理解深拷贝和浅拷贝,这里的深浅说简单一点就是拷贝后得到的新对象是否存在原对象的引用,深拷贝没有,而浅拷贝顾名思义则存在。在某些业务场景中,如果使用简单的浅拷贝则会是一个令人头大的问题,原因不多说,如上。在我看来,深拷贝与浅拷贝的本质区别就在于拷贝出来的新对象是否是一个独立的对象。
深拷贝原理:在内存中开辟了一段新的内存空间,用于存储copy出来的新对象。
浅拷贝原理:仅仅拷贝了基本数据类型,而对于引用类型的数据,复制后也会发生引用。
浅复制Example 1(利用for循环实现):
var Info={name:'mickey',array1:[1,2,3,4]};
var newInfo={};
for(let name in Info){
newInfo[name]=Info[name];
}
Info.array1.push(5);
console.log(Info); //{name:'mickey',array1:[1,2,3,4,5]};
console.log(newInfo); //{name:'mickey',array1:[1,2,3,4,5]};
浅复制Example 2 (利用寄生继承实现)
function clone(object){
let fn=function(){};
fn.prototype=object;
return new fn();
}
为了避免发生引用,我们从常见的两种引用数据类型出发去研究如何实现:
**
一、对于数组
1、利用Array.from(ReCopyArray)
var arrayA=[1,2,3,4,5];
var arrayB=Array.from(arrayA);
arrayA.push(6);
console.log(arrayA); //123456
console.log(arrayB); //12345
2、利用扩展运算符(…)
var arrayA=[12,3,45];
var arrayB=[...arrayA];
arrayA.push(9);
console.log(arrayA); //12,3,45,9
console.log(arrayB); //12,3,45
3、通过循环来进行复制
var arrayA=[1,2,3,4,5];
var arrayB=[];
for(let i=0;i<arrayA.length;i++){
arrayB[i]=arrayA[i]
}
arrayA.push(6);
console.log(arrayA); //123456;
console.log(arrayB); //12345
二、对于对象
1、简单点就是利用Object.assign({},ReCopyObject)
var stuInfo={name:'john',awardInfo:[{awardName:'全国互联网+大赛',awardAgency:'教育部',member:['mickey','kayu','doland']}]};
var newStuInfo=Object.assign({},stuInfo);
stuInfo.guideTeacher='hex';
console.log(stuInfo); //{name:'john',guideTeacher:'hex',awardInfo:[{awardName:'全国互联网+大赛',awardAgency:'教育部',member:['mickey','kayu','doland']}]};
console.log(newStuInfo) //{name:'john',awardInfo:[{awardName:'全国互联网+大赛',awardAgency:'教育部',member:['mickey','kayu','doland']}]};
注释:
1、Object.assign()需要谨慎使用,因为它的深拷贝只是比浅拷贝深拷贝了一层,也就是说,它只是一级属性的复制。
2、Object.assign()还可以用来合并多个对象。
2、利用递归实现
var stuInfo={name:'john',awardInfo:[{awardName:'全国互联网+大赛',awardAgency:'教育部',member:['mickey','kayu','doland']}]};
function deepCopy(source,target){
var target=target||{};
for(let key of Object.keys(source)){
//如果是对象则递归复制
if(typeof source[key]==='object'){
//判断obj1的构造函数是数组还是对象,依次对应所要复制的对象
target[key]=source[key].constructor==='Array'?[]:{};
//递归实现
deepCopy(source[key],target[key]);
}
//如果不是对象则普通赋值
else{
target[key]=source[key];
}
}
//将复制好的对象return出去
return target;
}
var newStuInfo={};
deepCopy(stuInfo,newStuInfo);
stuInfo.gudeTeacher='hex';
console.log(stuInfo); //{name:'john',guideTeacher:'hex',awardInfo:[{awardName:'全国互联网+大赛',awardAgency:'教育部',member:['mickey','kayu','doland']}]};
console.log(newStuInfo); //{name:'john',awardInfo:[{awardName:'全国互联网+大赛',awardAgency:'教育部',member:['mickey','kayu','doland']}]};
3、利用JSON.parse(JSON.stringify(obj))实现
let o1={
a:1
b:[2,3,4],
c:{
inner:'c'
}
}
let copyo1=JSON.parse(JSON.stringify(o1))
console.log(copyo1)
输出:
let o1={
a:1
b:[2,3,4],
c:{
inner:'c'
}
}
这种方法的本质就是将一个对象首先转化成一个字符串,然后在转化成对象。
注释:这种方法也不是万能的,比如当需要转化的对象属性为函数
或者属性值为undefined
的时候,转化之后得到的新对象这两种类型都会被忽略。
三、总结
- 值类型的赋值本质就是克隆,而引用类型的赋值仅仅是克隆了真正对象的内存地址
- ==会在比较的时候隐式地进行类型的转化,而===不会
- 实现深复制的关键在于,对于引用类型,我们只需要将其递归遍历,拆分为基本的值类型,就OK了
参考自JavaScript中的基本数据类型
关于数组对象中的Array.from方法的具体使用,请参考MDN中的解释:链接