原生js深刻理解系列(四)--- 多個實例深刻理解js的深拷貝和淺拷貝,多種方法實現對象的深拷貝

親們爲何要研究深拷貝和淺拷貝呢,由於咱們項目開發中有許多狀況須要拷貝一個數組抑或是對象,可是單純的靠=「賦值」並不會解決全部問題,若是遇到引用類型的對象改變新賦值的對象會形成原始對象也發生一樣改變,而要去除影響就必須用到淺拷貝、深拷貝,深拷貝,對於引用對象須要進行深拷貝纔會去除影響。若是是值類型直接「=」就好。es6

簡而言之:數組

賦值:就是兩個對象指向的內存地址同樣,a=b賦值後的新對象也指向同一個存儲地址因此b變化a跟隨變化,函數

淺拷貝:拷貝對象的一級元素的地址,若是一級元素所有爲值類型就會互不干擾,若是一級元素有引用類型,改變引用類型的裏面的值,會改變原對象。學習

深拷貝:拷貝對象各級元素的存儲地址。prototype

1.引用類型  引用類型一般叫作類(class),也就是說,遇到引用值,所處理的就是對象。new 出來的對象都是引用對象3d

(1)值類型:String, 數值、布爾值、null、undefined。 對於值類型一個對象一個存儲位置因此會互不干擾指針

(2)引用類型:對象、數組、函數。對於引用類型,a=b賦值後的新對象也指向同一個存儲地址因此b變化a跟隨變化,code

下圖若是a是{key:'1', value:'value',children:{key:'child'}}這樣一個對象,a賦值給b的話,a,b的存儲位相同,裏面的值變化也相同。若是a淺拷貝給b,a的值類型的值會複製給b,而a和b指向的引用類型的存儲位置會相同,a引用類型裏的children.key的值變化會引發b的children.key的值變化。即指針指向位置相同,那麼該位置裏的值也相同對象

而要改變這種狀況須要改變b的指向,使其指向b存儲位置以下圖blog

下面是各類實例對比:

第一模塊-數組:

數組的賦值:

a =[1,2,3];
b=a;
b.push('change');
console.log('a:'+a,'b:'+b)
// 結果 VM529:1 a:1,2,3,change    b:1,2,3,change, 數組元素值的變化會互相影響

數組淺拷貝:// 淺拷貝,拷貝的是屬性值。假如源對象的屬性值是一個對象的引用,那麼它也只指向那個引用

還有淺拷貝數組的話能夠利用,splice() 和 slice() 這兩個方法。他們的區別一個是splice能夠改變數組自己,slice不能改變數組自己。

Array.from() 方法從一個相似數組或可迭代對象中建立一個新的,淺拷貝的數組實例。

數組深拷貝:// 深拷貝,遍歷到到每一項都是值類型時能夠直接賦值具體實現參考本文下面對象深拷貝。下面代碼代碼是簡單的遍歷數組沒有進行處理數組裏面嵌套數組和對象的狀況。

a =[1,2,3];
let b =[]
a.forEach(val => b.push(val));b.push('change');
console.log('a:'+a,'b:'+b)
// 結果VM167:5 a:1,2,3      b:1,2,3,change

第二模塊-對象:

1-1對象賦值的代碼及結果

bb改變後原數組aa也跟隨變化,根本緣由就是像第一張線框圖描述的同樣

1-2對象對象淺拷貝的代碼及結果代碼及結果,使用es6的Object.assign()方法

淺拷貝bb不會影響aa,由於改變是是值類型,可是若是是改變引用類型的值呢?以下圖

結果很顯然,對於引用對象Object.assign()就不行了,有點雞肋了。aa的children裏面的key值隨bb的改變而改變

1-3對象深拷貝使用JSON.parse(JSON.stringify(obj)),即對象序列化

給子對象children改變屬性以下圖所示,aa原對象是不變的。可是JSON.parse(JSON.stringify(obj))實現深拷貝會存在一些問題,好比序列化會將序列化的undefined丟失,序列化正則對象(RegExp)會返回{},obj裏有NaN、Infinity和-Infinity,則序列化的結果會變成null,對於序列化構造函數construct()會被丟失
 

1-4對象深拷貝使用es6的Object.keys(obj),Object.values(obj)分別得到鍵數組和值數組,再經過函數根據條件循環回調deal()獲得深拷貝值:《推薦使用回調》,留下個小問題,看官們能夠試着這個思路去完成數組和函數回調實現深拷貝。

可複製代碼:

aa = {
    key:'1',
    value:'a',
    children:{
        key: '2',
        value: 'b'
    }
};

function deal(obj, bb = {}) {
    let keyArr = Object.keys(obj);
    let valueArr = Object.values(obj);
    valueArr.forEach((val,index) => {
    console.log(Object.prototype.toString.call(val))
        if(Object.prototype.toString.call(val) === "[object Object]") {
             bb[keyArr[index]] = deal(val, bb[keyArr[index]])
        }else {
            bb[keyArr[index]] = val
        }
    })
    return bb
}
BB = {}
BB=deal(aa);
console.log(BB)
BB['add']='addStr';
BB['children']['change']='變'
console.log('aa:',aa,'========','BB:',BB)

截圖代碼和結果:

第三模塊對於對象來講可複製的淺、深拷貝代碼和結果事例以下:

實例一

aa = {
    key:'1',
    value:'a',
    children:{
        key: '2',
        value: 'b'
    }
};
bb=aa;
bb['add']='addStr';
console.log('aa:',aa,'====','bb',bb) 
// 下面結果出現aa,bb都有add屬性,使用等號沒法實現對象的拷貝,這個一樣適用於數組

VM1098:9 aa: {key: "1", value: "a", children: {…}, add: "addStr"}add: "addStr"children: key: "2"value: "b"proto: Objectkey: "1"value: "a"proto: Object ==== bb {key: "1", value: "a", children: {…}, add: "addStr"}add: "addStr"children: key: "2"value: "b"proto: Objectkey: "1"value: "a"proto: Object

實例二

aa = {
   key:'1',
    value:'a',
    children:{
        key: '2',
        value: 'b'
    }
};
bb = {}
bb=Object.assign({},aa);
bb['add']='addStr';
console.log('aa:',aa,'====','bb',bb)
// 下面結果出現aa沒有add屬性,bb有add屬性,使用assign能夠實現對象的第一級鍵值對的拷貝。

VM1223:10 aa: {key: "1", value: "a", children: {…}}children: key: "2"value: "b"proto: Objectkey: "1"value: "a"proto: Object ==== bb {key: "1", value: "a", children: {…}, add: "addStr"}add: "addStr"children: key: "2"value: "b"proto: Objectconstructor: ƒ Object()hasOwnProperty: ƒ hasOwnProperty()isPrototypeOf: ƒ isPrototypeOf()propertyIsEnumerable: ƒ propertyIsEnumerable()toLocaleString: ƒ toLocaleString()toString: ƒ toString()valueOf: ƒ valueOf()defineGetter: ƒ defineGetter()defineSetter: ƒ defineSetter()lookupGetter: ƒ lookupGetter()lookupSetter: ƒ lookupSetter()get proto: ƒ proto()set proto: ƒ proto()key: "1"value: "a"proto: Object

對於改變引用對象源碼

實例以下,淺拷貝改變引用對象裏面的值事,原對象也會改變

aa = {
    key:'1',
    value:'a',
    children:{
        key: '2',
        value: 'b'
    }
};
bb = {}
bb=Object.assign({},aa);
bb['children']['key']='addStr';
console.log('aa:',aa,'====','bb',bb) // ,結果以下:
// 下面結果出現aa,bb的引用屬性children裏面的key屬性都變成了addStr,使用assign不能夠實現對象裏引用屬性的拷貝,所以assign()爲淺拷貝。由於 Object.assign()拷貝的是屬性值。假如源對象的屬性值是一個對象的引用,那麼它也只指向那個引用。

在這裏插入圖片描述

實例三

aa = {
    key:'1',
    value:'a',
    children:{
        key: '2',
        value: 'b'
    }
};
bb = {}
bb=JSON.parse(JSON.stringify(aa));
bb['add']='addStr';
bb.children.key='key';
console.log('aa:',aa,'====','bb',bb)
// 下面結果出現aa保持初始狀態,bb變成修改後的狀態,使用JSON.parse(JSON.stringify(aa))爲深拷貝。

歡迎轉載,轉載請註明出處。歡迎你們交流學習

相關文章
相關標籤/搜索