深拷貝 淺拷貝
(給前端大學(xué)加星標(biāo),提升前端技能.)
轉(zhuǎn)自:掘金 - 尤雨溪的大迷弟
回顧一下老知識,記個筆記~
首先說下堆棧,基本數(shù)據(jù)類型與引用數(shù)據(jù)類型,深拷貝與淺拷貝與此相關(guān)。
一、基本數(shù)據(jù)類型 和 引用數(shù)據(jù)類型
1.變量類型分為兩類:
基本數(shù)據(jù)類型:number,string,boolean,null,undefined,symbol
引用數(shù)據(jù)類型:統(tǒng)稱為Object類型,細分的話,有:Object,Array,Date,Function等。
2.存儲方式:
a.基本數(shù)據(jù)類型保存在棧內(nèi)存,形式如下:棧內(nèi)存中分別存儲著變量的標(biāo)識符以及變量的值。
例:
let?a?=?'A';

b.引用數(shù)據(jù)類型保存在棧內(nèi)存,形式如下:名存在棧內(nèi)存中,值存在于堆內(nèi)存中,但是棧內(nèi)存會提供一個引用的地址指向堆內(nèi)存中的值。
例:
let?a?=?{name:“A”};

3.不同類型的復(fù)制方式:
a.基本數(shù)據(jù)類型:
let?a?=?1;當(dāng)你let?b = a 時,棧內(nèi)存會新開辟一個內(nèi)存,例如這樣:

此時改變 a 變量的值,并不會影響 b 的值。
b.引用數(shù)據(jù)類型:
let?a?=?{name:?'A',age:?10};
let?b?=?a;
a.age?=?20;
//?此時改變a的值,會改變b的值,此時內(nèi)存中是這樣的:

二、淺拷貝 和 深拷貝
淺拷貝:創(chuàng)建一個新的數(shù)據(jù),這個數(shù)據(jù)有著原始數(shù)據(jù)屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型,拷貝的就是內(nèi)存地址,所以如果其中一個數(shù)據(jù)改變了這個地址,就會影響到另一個數(shù)據(jù)。
可以說
淺拷貝只解決了數(shù)據(jù)第一層的問題,拷貝第一層的基本類型值,以及第一層的引用類型地址
深拷貝:深拷貝會拷貝所有的屬性,并拷貝屬性指向的動態(tài)分配的內(nèi)存。當(dāng)對象和它所引用的對象一起拷貝時即發(fā)生深拷貝。深拷貝相比于淺拷貝速度較慢并且花銷較大。在堆中重新分配內(nèi)存,擁有不同的地址,且值是一樣的,復(fù)制后的對象與原來的對象是完全隔離,互不影響。
三、實現(xiàn)深拷貝
1.數(shù)據(jù)只有一層的時候:Object.assign()方法可以把任意多個的源對象自身的可枚舉屬性拷貝給目標(biāo)對象,然后返回目標(biāo)對象。但是 Object.assign() 進行的是淺拷貝,拷貝的是對象的屬性的引用,而不是對象本身。當(dāng)數(shù)據(jù)只有一層的時候,是深拷貝。
相同的還有數(shù)組方法
slice、concat,他們都為淺拷貝,當(dāng)數(shù)據(jù)只有一層的時候,可實現(xiàn)深拷貝的效果
例:
let?a=[1,2,3,4,{age:?1}];
let?b=Object.assign([],a);
a[0]=2;
a[4].age=2;
console.log(a,b);
//?(5)?[2,?2,?3,?4,?{…}]
//?0:?2
//?1:?2
//?2:?3
//?3:?4
//?4:?{age:?2}
//?length:?5
//?__proto__:?Array(0)
?
//?(5)?[1,?2,?3,?4,?{…}]
//?0:?1
//?1:?2
//?2:?3
//?3:?4
//?4:?{age:?2}
//?length:?5
//?__proto__:?Array(0)
2.簡單的遞歸函數(shù):
function?deepClone(obj){
????let?objClone?=?Array.isArray(obj)?[]:{};
????if(obj?&&?typeof?obj==="object"){
????????for(key?in?obj){
????????????if(obj.hasOwnProperty(key)){
????????????????//判斷obj子元素是否為對象,如果是,遞歸復(fù)制
????????????????if(obj[key]&&typeof?obj[key]?==="object"){
????????????????????objClone[key]?=?deepClone(obj[key]);
????????????????}else{
????????????????????//如果不是,簡單復(fù)制
????????????????????objClone[key]?=?obj[key];
????????????????}
????????????}
????????}
????}
????return?objClone;
}????
3.JSON.parse(JSON.stringify())用JSON.stringify將對象轉(zhuǎn)成JSON字符串,再用JSON.parse()把字符串解析成對象,一去一來,新的對象產(chǎn)生了,而且對象會開辟新的棧,實現(xiàn)深拷貝。
let?a=[1,2,3,4,{age:?1}];
let?b=JSON.parse(JSON.stringify(a));
a[0]=2;
a[4].age=2;
console.log(a,b);
//?(5)?[2,?2,?3,?4,?{…}]
//?0:?2
//?1:?2
//?2:?3
//?3:?4
//?4:?{age:?2}
//?length:?5
//?__proto__:?Array(0)
?
//?(5)?[1,?2,?3,?4,?{…}]
//?0:?1
//?1:?2
//?2:?3
//?3:?4
//?4:?{age:?1}
//?length:?5
//?__proto__:?Array(0)
該方法有幾個缺陷:
1、會忽略undefined、symbol和函數(shù),例:
let?obj?=?{
????name:?'A',
????name1:?undefined,
????name3:?function()?{},
????name4:??Symbol('A')
}
let?obj2?=?JSON.parse(JSON.stringify(obj));
console.log(obj2);?//?{name:?"A"}
2、對象循環(huán)引用時,會報錯。例:
??let?obj?=?{
??????name1:?'A',
??????name2:?{
??????????name3:?'B'
??????},
??}
??obj.name1?=?obj.name2;
??obj.name2.name3?=?obj.name1;
??let?obj2?=?JSON.parse(JSON.stringify(obj));
??console.log(obj2);?//?Converting?circular?structure?to?JSON
3、new Date,轉(zhuǎn)換結(jié)果不正確
4、正則會被忽略
MDN的解釋,JSON.stringify() 將值轉(zhuǎn)換為相應(yīng)的JSON格式:
轉(zhuǎn)換值如果有 toJSON() 方法,該方法定義什么值將被序列化。 非數(shù)組對象的屬性不能保證以特定的順序出現(xiàn)在序列化后的字符串中。 布爾值、數(shù)字、字符串的包裝對象在序列化過程中會自動轉(zhuǎn)換成對應(yīng)的原始值。 undefined、任意的函數(shù)以及 symbol 值,在序列化過程中會被忽略(出現(xiàn)在非數(shù)組對象的屬性值中時)或者被轉(zhuǎn)換成 null(出現(xiàn)在數(shù)組中時)。函數(shù)、undefined 被單獨轉(zhuǎn)換時,會返回 undefined,如JSON.stringify(function(){}) or JSON.stringify(undefined). 對包含循環(huán)引用的對象(對象之間相互引用,形成無限循環(huán))執(zhí)行此方法,會拋出錯誤。 所有以 symbol 為屬性鍵的屬性都會被完全忽略掉,即便 replacer 參數(shù)中強制指定包含了它們。 Date 日期調(diào)用了 toJSON() 將其轉(zhuǎn)換為了 string 字符串(同Date.toISOString()),因此會被當(dāng)做字符串處理。 NaN 和 Infinity 格式的數(shù)值及 null 都會被當(dāng)做 null。 其他類型的對象,包括 Map/Set/WeakMap/WeakSet,僅會序列化可枚舉的屬性。
MDN地址:developer.mozilla.org/zh-CN/docs/…
網(wǎng)上看到的一個小題目:
var?a?=?{n:?1};
var?b?=?a;
a.x?=?a?=?{n:?2};
console.log(a);?//?{n:?2}
console.log(b);?//?{n:1,?x:?{n:?2}}
個人理解:
var a = {n: 1}棧中給a開辟了一個引用地址,指向堆中的對象{n: 1}var b = a棧中給b開辟了一個引用地址,指向堆中的對象{n: 1}.的優(yōu)先級高于=,所以先執(zhí)行a.x,堆內(nèi)存中的{n: 1}就會變成{n: 1, x: undefined},改變之后相應(yīng)的b.x也變化了,因為指向的是同一個對象。
賦值操作是從右到左,所以先執(zhí)行a = {n: 2},a的引用就被改變了,然后這個返回值又賦值給了a.x,需要注意的是這時候a.x是第一步中的{n: 1, x: undefined}那個對象,其實就是b.x,相當(dāng)于b.x = {n: 2}
