天天看點

原生js深入了解系列(四)--- 多個執行個體深入了解js的深拷貝和淺拷貝,多種方法實作對象的深拷貝

親們為什麼要研究深拷貝和淺拷貝呢,因為我們項目開發中有許多情況需要拷貝一個數組抑或是對象,但是單純的靠=“指派”并不會解決所有問題,如果遇到引用類型的對象改變新指派的對象會造成原始對象也發生同樣改變,而要去除影響就必須用到淺拷貝、深拷貝,深拷貝,對于引用對象需要進行深拷貝才會去除影響。如果是值類型直接“=”就好。

簡而言之:

指派:就是兩個對象指向的記憶體位址一樣,a=b指派後的新對象也指向同一個存儲位址是以b變化a跟随變化,

淺拷貝:拷貝對象的一級元素的位址,如果一級元素全部為值類型就會互不幹擾,如果一級元素有引用類型,改變引用類型的裡面的值,會改變原對象。

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

1.引用類型 引用類型通常叫做類(class),也就是說,遇到引用值,所處理的就是對象。new 出來的對象都是引用對象

(1)值類型:String, 數值、布爾值、null、undefined。 對于值類型一個對象一個存儲位置是以會互不幹擾

(2)引用類型:對象、數組、函數。對于引用類型,a=b指派後的新對象也指向同一個存儲位址是以b變化a跟随變化,

下圖如果a是{key:‘1’, value:‘value’,children:{key:‘child’}}這樣一個對象,a指派給b的話,a,b的存儲位相同,裡面的值變化也相同。如果a淺拷貝給b,a的值類型的值會複制給b,而a和b指向的引用類型的存儲位置會相同,a引用類型裡的children.key的值變化會引起b的children.key的值變化。即指針指向位置相同,那麼該位置裡的值也相同

原生js深入了解系列(四)--- 多個執行個體深入了解js的深拷貝和淺拷貝,多種方法實作對象的深拷貝

而要改變這種情況需要改變b的指向,使其指向b存儲位置如下圖

原生js深入了解系列(四)--- 多個執行個體深入了解js的深拷貝和淺拷貝,多種方法實作對象的深拷貝

下面是各種執行個體對比:

第一子產品-數組:

數組的指派:

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對象指派的代碼及結果

原生js深入了解系列(四)--- 多個執行個體深入了解js的深拷貝和淺拷貝,多種方法實作對象的深拷貝

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

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

原生js深入了解系列(四)--- 多個執行個體深入了解js的深拷貝和淺拷貝,多種方法實作對象的深拷貝

淺拷貝bb不會影響aa,因為改變是是值類型,但是如果是改變引用類型的值呢?如下圖

原生js深入了解系列(四)--- 多個執行個體深入了解js的深拷貝和淺拷貝,多種方法實作對象的深拷貝

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

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

原生js深入了解系列(四)--- 多個執行個體深入了解js的深拷貝和淺拷貝,多種方法實作對象的深拷貝

給子對象children改變屬性如下圖所示,aa原對象是不變的。但是JSON.parse(JSON.stringify(obj))實作深拷貝會存在一些問題,比如序列化會将序列化的undefined丢失,序列化正則對象(RegExp)會傳回{},obj裡有NaN、Infinity和-Infinity,則序列化的結果會變成null,對于序列化構造函數construct()會被丢失

原生js深入了解系列(四)--- 多個執行個體深入了解js的深拷貝和淺拷貝,多種方法實作對象的深拷貝

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)
           

截圖代碼和結果:

原生js深入了解系列(四)--- 多個執行個體深入了解js的深拷貝和淺拷貝,多種方法實作對象的深拷貝

第三子產品對于對象來說可複制的淺、深拷貝代碼和結果事例如下:

執行個體一

aa = {
    key:'1',
    value:'a',
    children:{
        key: '2',
        value: 'b'
    }
};
bb=aa;bb['add']='addStr';console.log('aa:',aa,'====','bb',bb)
           

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)

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),結果如下:
           
原生js深入了解系列(四)--- 多個執行個體深入了解js的深拷貝和淺拷貝,多種方法實作對象的深拷貝

執行個體三

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))為深拷貝。

原生js深入了解系列(四)--- 多個執行個體深入了解js的深拷貝和淺拷貝,多種方法實作對象的深拷貝

歡迎轉載,轉載請注明出處。歡迎大家交流學習