js中的浅拷贝和深拷贝
js的数据类型:基本数据类型:String、Number、Boolean、Null、Undefined;引用数据类型:Object(Array、Date、Function、RegExp)。
基本数据类型和引用数据类型的区别:基本数据类型:存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问。引用数据类型:引用类型(object)是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况来进行特定的分配。引用数据类型保存在堆内存中,然后在栈内存中保存了一个对堆内存中实际对象的引用,即数据在堆内存中的地址,JS对引用数据类型的操作都是操作对象的引用而不是实际的对象,如果obj1拷贝了obj2,那么这两个引用数据类型就指向了同一个堆内存对象,具体操作是obj1将栈内存的引用地址复制了一份给obj2,因而它们共同指向了一个堆内存对象;
例如:vara1;ba;栈内存会开辟一个新的内存空间,此时b和a都是相互独立的b2;console。log(a);1
例如:vararr〔1,2,3,4〕;arr1arr;arr将栈内存的引用地址复制了一份给arr1,因而它们共同指向了一个堆内存对象;arr1〔2〕9;console。log(arr);〔1,2,9,4〕
基本数据类型值不可变:javascript中的原始值(undefined、null、布尔值、数字和字符串)与对象(包括数组和函数)有着根本区别。原始值是不可更改的:任何方法都无法更改(或突变)一个原始值。对数字和布尔值来说显然如此改变数字的值本身就说不通,而对字符串来说就不那么明显了,因为字符串看起来像由字符组成的数组,我们期望可以通过指定索引来假改字符串中的字符。实际上,javascript是禁止这样做的。字符串中所有的方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值。
基本数据类型的值是不可变的,动态修改了基本数据类型的值,它的原始值也是不会改变的,varstrabc;str〔1〕f;console。log(str);abc
有很多操作字符串的方法,但是这些方法都是返回一个新的字符串,并没有改变其原有的数据。
引用类型值可变vara〔1,2,3〕;a〔1〕5;console。log(a〔1〕);5
浅拷贝与深拷贝:浅拷贝和深拷贝都只针对于引用数据类型,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存;但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象;浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制。
区分深拷贝与浅拷贝,就是假设B复制了A,当修改B时,看A是否会发生变化,如果A也跟着变了,说明这是浅拷贝,拿人手短,如果A没变,那就是深拷贝,自食其力。
浅拷贝:
forin只循环第一层functionsimpleCopy(obj1){varobj2Array。isArray(obj1)?〔〕:{};for(letiinobj1){obj2〔i〕obj1〔i〕;}returnobj2;}varobj1{a:1,b:2,c:{d:3}}varobj2simpleCopy(obj1);obj2。a3;obj2。c。d4;console。log(obj1。a);1console。log(obj2。a);3console。log(obj1。c。d);4console。log(obj2。c。d);4
Object。assign()实现浅拷贝及一层的深拷贝varobj1{a:{b:1},c:2}varobj2Object。assign({},obj1)obj2。a。b3;obj2。c3console。log(obj1。a。b);3console。log(obj2。a。b);3console。log(obj1。c);2console。log(obj2。c);3
Object实现拷贝2,浅拷贝varobj1{a:1,b:2,c:{d:3}}varobj2Object。create(obj1);obj2。a3;obj2。c。d4;console。log(obj1。a);1console。log(obj2。a);3console。log(obj1。c。d);4console。log(obj2。c。d);4
深拷贝
1、递归实现深拷贝functiondeepCopy(obj1){varobj2Array。isArray(obj1)?〔〕:{};if(obj1typeofobj1object){for(variinobj1){varpropobj1〔i〕;避免相互引用造成死循环,如obj1。aobjif(propobj1){continue;}if(obj1。hasOwnProperty(i)){如果子属性为引用数据类型,递归复制if(proptypeofpropobject){obj2〔i〕(prop。constructorArray)?〔〕:{};arguments。callee(prop,obj2〔i〕);递归调用}else{如果是基本数据类型,只是简单的复制obj2〔i〕prop;}}}}returnobj2;}varobj1{a:1,b:2,c:{d:3}}varobj2deepCopy(obj1);obj2。a3;obj2。c。d4;console。log(obj1。a);1console。log(obj2。a);3console。log(obj1。c。d);3console。log(obj2。c。d);4
2、Object。create实现深拷贝1,但也只能拷贝一层functiondeepCopy(obj1){varobj2Array。isArray(obj1)?〔〕:{};if(obj1typeofobj1object){for(variinobj1){varpropobj1〔i〕;避免相互引用造成死循环,如obj1。aobjif(propobj1){continue;}if(obj1。hasOwnProperty(i)){如果子属性为引用数据类型,递归复制if(proptypeofpropobject){obj2〔i〕(prop。constructorArray)?〔〕:Object。create(prop);}else{如果是基本数据类型,只是简单的复制obj2〔i〕prop;}}}}returnobj2;}varobj1{a:1,b:2,c:{d:3}}varobj2deepCopy(obj1);obj2。a3;obj2。c。d4;console。log(obj1。a);1console。log(obj2。a);3console。log(obj1。c。d);3console。log(obj2。c。d);4
3、使用JSON。stringify和JSON。parse实现深拷贝JSON。stringify把对象转成字符串,再用JSON。parse把字符串转成新的对象。缺点:(1)如果obj里面有时间对象,则JSON。stringify后再JSON。parse的结果,时间将只是字符串的形式,而不是时间对象;(2)如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象;(3)如果obj里有函数,undefined,则序列化的结果会把函数或undefined丢失;(4)如果obj里有NaN、Infinity和Infinity,则序列化的结果会变成null;(5)JSON。stringify()只能序列化对象的可枚举的自有属性,例如如果obj中的对象是有构造函数生成的,则使用JSON。parse(JSON。stringify(obj))深拷贝后,会丢弃对象的constructor;(6)如果对象中存在循环引用的情况也无法正确实现深拷贝;
以上,如果拷贝的对象不涉及上面讲的情况,可以使用JSON。parse(JSON。stringify(obj))实现深拷贝,但是涉及到上面的情况(除循环引用的情况外),可以考虑使用如下方法实现深拷贝。functiondeepClone(data){consttypethis。judgeType(data);letobj;if(typearray){obj〔〕;}elseif(typeobject){obj{};}else{不再具有下一层次returndata;}if(typearray){eslintdisablenextlinefor(leti0,lendata。length;ilen;i){obj。push(this。deepClone(data〔i〕));}}elseif(typeobject){对原型上的方法也拷贝了。。。。eslintdisablenextlinefor(constkeyindata){obj〔key〕this。deepClone(data〔key〕);}}returnobj;}functionjudgeType(obj){tostring会返回对应不同的标签的构造函数consttoStringObject。prototype。toString;constmap{〔objectBoolean〕:boolean,〔objectNumber〕:number,〔objectString〕:string,〔objectFunction〕:function,〔objectArray〕:array,〔objectDate〕:date,〔objectRegExp〕:regExp,〔objectUndefined〕:undefined,〔objectNull〕:null,〔objectObject〕:object,};if(objinstanceofElement){returnelement;}returnmap〔toString。call(obj)〕;}
4、如果被拷贝中没有对时间、正则要求兼容,可以采用如下方法functiondeepClone(obj){letobjCloneArray。isArray(obj)?〔〕:{};if(objtypeofobjobject){for(keyinobj){if(obj。hasOwnProperty(key)){判断ojb子元素是否为对象,如果是,递归复制if(obj〔key〕typeofobj〔key〕object){objClone〔key〕deepClone(obj〔key〕);}else{如果不是,简单复制objClone〔key〕obj〔key〕;}}}}returnobjClone;}
5、热门的函数库lodash,也有提供。cloneDeep用来做深拷贝
6、jquery实现深拷贝,jquery提供一个。extend可以用来做深拷贝varrequire(jquery);varobj1{a:1,b:{f:{g:1}},c:〔1,2,3〕};varobj2。extend(true,{},obj1);console。log(obj1。b。fobj2。b。f);false