深拷貝VS淺拷貝
本文主要對(duì)深拷貝&淺拷貝的解釋及實(shí)現(xiàn)做一下簡(jiǎn)單記錄。
之所以會(huì)有深拷貝與淺拷貝之分庄拇,是因?yàn)椴煌瑪?shù)據(jù)類型的數(shù)據(jù)在內(nèi)存中的存儲(chǔ)區(qū)域不一樣。
堆和棧是計(jì)算機(jī)中劃分出來用來存儲(chǔ)的區(qū)域,其中堆(heap)則是動(dòng)態(tài)分配的內(nèi)存瓤狐,大小不定也不會(huì)自動(dòng)釋放;而棧(stack)為自動(dòng)分配的內(nèi)存空間批幌,它由系統(tǒng)自動(dòng)釋放础锐。存放在棧內(nèi)存中的簡(jiǎn)單數(shù)據(jù)段,數(shù)據(jù)大小確定荧缘,內(nèi)存空間大小可以分配皆警,是直接按值存放的,所以可以直接訪問截粗。
眾所周知信姓,JavaScript中的數(shù)據(jù)分為(基本類型和引用類型)。五種基本類型(boolean
,number
,undefined
,null
,string
,)的數(shù)據(jù)的原始值是大小確定不可變的,所以放在棧內(nèi)存中绸罗;而引用類型(object)是放在堆內(nèi)存中的意推,對(duì)應(yīng)賦值的變量只是一個(gè)存放在棧內(nèi)存中的指針,也就是一個(gè)指向引用類型存放在堆內(nèi)存中的地址珊蟀。
引用類型比較與基本類型比較的區(qū)別
引用類型是引用的比較菊值,例如
// a與b是兩個(gè)引用類型,但其指針指向的是兩個(gè)不同的引用
let a = [1,2,3];
let b = [1,2,3];
a===b; //false
// 引用類型的賦值操作,b的指針也是指向與a同樣的引用
let a = [1,2,3];
let b = a;
a===b; //true
而基本類型間的比較是值的比較腻窒,例如
let a = 1;
let b = 1;
a===b; //true
let a = 1;
let b = a;
a===b; //true
賦值操作:傳值與傳址的區(qū)別
在對(duì)基本類型進(jìn)行賦值操作的時(shí)候?qū)嶋H是傳值昵宇,即在棧內(nèi)存中新開一個(gè)區(qū)域給新的變量,然后再把值賦給它儿子。所以基本類型賦值的變量是兩個(gè)互相獨(dú)立變量瓦哎。
let a = 10;
let b = a;
b++;
console.log(a); //10
console.log(b); //11
而引用類型的賦值操作是傳址,只是在棧內(nèi)存中新增了一個(gè)變量典徊,同時(shí)賦值給這個(gè)變量的只是保存在堆內(nèi)存中對(duì)象的地址杭煎,也就是指針的指向。因此這兩個(gè)變量的指針指向同一個(gè)地址卒落,相互操作也就會(huì)有影響羡铲。
let a = {};
let b = a;
a.name = 'slevin';
console.log(b.name); //'slevin'
console.log(a.name); //'slevin'
console.log(a===b); //true
需要注意的是,引用類型的賦值并不算是淺拷貝儡毕,因?yàn)橘x值操作只相當(dāng)于是引用也切,兩個(gè)變量還是指向同一對(duì)象,任何一方改變了都會(huì)影響到另一方腰湾;但淺拷貝出來的變量與源變量已經(jīng)不同雷恃,在不包含子對(duì)象的情況下(此情況即為深拷貝),一方改變不會(huì)影響到另一方费坊。如下賦值操作:
let a = {};
let b = a;
b.name = 'slevin';
console.log(a.name); //'slevin';對(duì)b操作影響到了a
console.log(a===b); //true倒槐;因?yàn)閮烧咧赶蛲粋€(gè)對(duì)象
淺拷貝實(shí)現(xiàn)方法
-
自定義實(shí)現(xiàn)方法:
// 淺拷貝的方法 function shallowCopy(srcObj) { let copy = {}; for (let key in srcObj) { //只拷貝自身的屬性,__proto__上繼承來的屬性不做拷貝附井,也可去掉這個(gè)限制 if (srcObj.hasOwnProperty(key)) { copy[key] = srcObj[key]; } } return copy; } let obj = { name: 'slevin', age: 18, language: { 'english': 'good', 'mandarin': 'wonderful', } } let copy = shallowCopy(obj); copy.age = 28; console.log(obj.age); // 18; 并沒有改變?cè)磳?duì)象 console.log(obj === copy); //false;兩者指向不是同一個(gè)對(duì)象
-
利用
Object.assign(target,...sources)
可將一個(gè)或多個(gè)源對(duì)象上可枚舉屬性的值復(fù)制到目標(biāo)對(duì)象讨越,并返回目標(biāo)對(duì)象。但注意永毅,只能做一層屬性的淺拷貝把跨。let obj = { name: 'slevin', age: 18, language: { english: 'good', mandarin: 'wonderful', test: [1, 2, 3] }, fn:function(){ console.log('this:',this.name); } } let copy = Object.assign({},obj); //不會(huì)改變?cè)磳?duì)象 copy.age = 22; //會(huì)改變?cè)磳?duì)象 copy.language.english = 'bad'; console.log('copy===obj:',copy===obj);//false console.log('copy:',copy); console.log('obj:',obj);
-
對(duì)于數(shù)組來說,也可以使用
Array.prototype.slice()
方法和Array.prototype.concact()
方法let obj = [1,2,['a','b','c']] let copy = obj.slice(); // 不會(huì)改變?cè)磳?duì)象 copy[0]=111; // 會(huì)改變?cè)磳?duì)象 copy[2][0]='aaa'; console.log('copy===obj:',copy===obj);//false console.log('copy:',copy); console.log('obj:',obj);
深拷貝及實(shí)現(xiàn)方法
簡(jiǎn)單來說沼死,深拷貝就是把對(duì)象以及對(duì)象的子對(duì)象進(jìn)行拷貝着逐。因?yàn)闇\拷貝只復(fù)制了一層對(duì)象的屬性,而如果對(duì)象的數(shù)值也為引用類型意蛀,那么拷貝過來依然是個(gè)引用地址耸别,在拷貝對(duì)象上對(duì)子對(duì)象的值進(jìn)行操作會(huì)改變?cè)紨?shù)據(jù)中的值。
例如上面obj
淺拷貝得到copy對(duì)象
县钥,如果在copy
對(duì)象上改變子對(duì)象copy.language
上的屬性值太雨,會(huì)影響到源對(duì)象obj
:
copy.language.english = 'bad';
copy.language.mandarin = 'bad';
console.log(obj.language); // {'english': 'bad','mandarin': 'bad',}
那么如何深拷貝呢?思路就是遞歸調(diào)用剛剛的淺拷貝魁蒜,遍歷所有值為引用類型的屬性,然后依次賦值給另外一個(gè)對(duì)象即可。
-
方法一兜看,自定義實(shí)現(xiàn):
/**將源對(duì)象source深度合并到目標(biāo)對(duì)象target上 * source 源對(duì)象 * target 目標(biāo)對(duì)象锥咸,默認(rèn){} * deep 是否深度合并,默認(rèn)true */ function deepCopy (source,target={},deep=true) { for (let key in source){ // 深拷貝细移,而且只拷貝自身屬性.(默認(rèn)傳入的source為對(duì)象) if (deep && source.hasOwnProperty(key)){ if (typeof(source[key])==='object'){ // // source[key] 是對(duì)象搏予,而 target[key] 不是對(duì)象, 則 target[key] = {} 初始化一下弧轧,否則遞歸會(huì)出錯(cuò)的 // if (!(target[key] instanceof Object) && (source[key] instanceof Object)){ // target[key] = {}; // } // // source[key] 是數(shù)組雪侥,而 target[key] 不是數(shù)組,則 target[key] = [] 初始化一下精绎,否則遞歸會(huì)出錯(cuò)的 // if (!Array.isArray(target[key]) && Array.isArray(source[key])) { // target[key] = []; // } // 上面的代碼可以簡(jiǎn)化為以下: target[key] = Array.isArray(source[key]) ? [] : {}; // 遞歸執(zhí)行拷貝 deepCopy(source[key],target[key],true); } else { target[key] = source[key]; } } } return target; } let obj = { name: 'slevin', age: 18, language: { english: 'good', mandarin: 'wonderful', test:[1,2,3] } } let copy = deepCopy(obj); copy.language.test[0] = 111; console.log('copy:',copy); //111 改變目標(biāo)對(duì)象的子對(duì)象屬性值 console.log('obj:',obj); //1 對(duì)應(yīng)源對(duì)象上沒有改變
-
利用
JSON.parse(JSON.stringify(copyObj))
方法let obj = { name: 'slevin', age: 18, language: { english: 'good', mandarin: 'wonderful', test: [1, 2, 3] } } let copy = JSON.parse(JSON.stringify(obj)) copy.language.test[0]=111; console.log('copy===obj:',copy===obj);//false console.log('copy.language.test[0]:',copy.language.test[0]);//111 console.log('obj.language.test[0]:',obj.language.test[0]);//1
但要注意速缨,此方法有兩個(gè)缺點(diǎn):
- 如果源對(duì)象屬性值有函數(shù),無法拷貝下來
- 無法拷貝源對(duì)象原型鏈上的屬性和方法
let obj = { name: 'slevin', age: 18, language: { english: 'good', mandarin: 'wonderful', test: [1, 2, 3] }, fn:function(){ console.log('this:',this.name); } } let copy = JSON.parse(JSON.stringify(obj)); console.log('copy===obj:',copy===obj);//false console.log('obj.fn:',obj.fn); //fn(){}... console.log('copy.fn:',copy.fn); //undefined
最后代乃,再補(bǔ)一張引用類型的賦值操作旬牲、淺拷貝深拷貝對(duì)比圖,加深印象