首頁 > 軟體

JavaScript最完整的深淺拷貝實現方式詳解

2022-02-28 16:06:03

深淺拷貝:

記憶體中一共分為棧記憶體和堆記憶體兩大區域,所謂深淺拷貝主要是對js參照型別資料進行拷貝一份,淺拷貝就是參照型別資料相互賦值之後,例obj1=obj2;如果後面的操作中修改obj1或者obj2,這個時候資料是會進行相應的變化的,因為在記憶體中參照型別資料是儲存在堆記憶體中,堆記憶體中存放的是參照型別的值,同時會有一個指標地址指向棧記憶體,兩個參照型別資料地址一樣,如果其中一個發生變化另外一個都會有影響;而深拷貝則不會,深拷貝是會在堆記憶體中重新開闢一塊空間進行存放;

簡單來說就是B複製了A,如果A發生了改變,如果B隨之變化,那麼是淺拷貝,如果B並沒有發生變化,則是深拷貝。

基本型別拷貝

let a = 1;
let b = a;
b = 2;
console.log(a);//1
console.log(b);//2

a,b都是屬於基本型別,基本型別的複製是不會影響對方的,因為基本型別是每一次建立變數都會在棧記憶體中開闢一塊記憶體,用來存放值,所以對基本型別進行拷貝是不會對另外一個變數有影響的,屬於深拷貝。

陣列拷貝 

concat() slice()

//  concat()
let list = ['a','b','c'];
let list2 = list.concat();
list2.push('d')
console.log(list);//['a','b','c']
console.log(list2);//['a','b','c','d']
//  slice()
let list = ['a','b','c'];
let list2 = list.slice();
list2.push('d')
console.log(list);//['a','b','c']
console.log(list2);//['a','b','c','d']

上面兩種方法只能實現陣列型別裡面的單層深拷貝,如果是多層無法實現深拷貝。

//二維陣列
let list = ['a','b','c',['d','e','f']];
let list2 = list.concat();
list2[3][0] = 'a';
console.log(list);
console.log(list2);

可以看到原二維陣列的值也發生了變化,說明沒有實現深拷貝。

由此總結,concat和slice只能實現一維陣列的深拷貝,不能對多維陣列進行深拷貝,所以並不是一個完美的深拷貝方式。

物件拷貝

new Object()

let a = {id:1,name:'a',obj:{id:999}};
let b = new Object();
b.id = a.id;
b.name = a.name;
b.obj = a.obj;
a.name = 'b';
a.obj.id = 888;
console.log(a);
console.log(b);

 

a.name更改並沒有影響到b.name,好像可以看作深拷貝,但第二層obj裡面裡面的id隨之更改了,因此其實並不是深拷貝。

Object.assign

let a = {id:1,name:'a',obj:{id:999}};
function fun(obj){
    let o = {};
    Object.assign(o,obj);
    return o;
}
let a2 = fun(a);
a2.name ='a2';
a2.obj.id = 888;
console.log(a);
console.log(a2);

以上兩種方法,對於一層物件都能實現深拷貝,但對於多層物件則無法實現,因此也不是一種完美的深拷貝方式。

JSON.parse(JSON.stringify( ))

let a = {
    name : 'a',
    age : 20,
    obj : {id:999},
    action : function(){
        console.log(this.name);
    }
}
let b = JSON.parse(JSON.stringify(a));
a.name = 'b';
a.obj.id = 888;
console.log(a);
console.log(b);

單層物件name和多層物件obj.id的改變都沒影響到原物件,因此是一個比較完美的深拷貝,但是能看到function好像並沒有被拷貝。

可以看出這個方法對於一層和多層都能實現深拷貝,但是這個方法的缺陷是不能拷貝Function,所以在使用時,一定要注意資料型別。

遞迴

let a = {
    name:'a',
    skin:["red","blue","yellow",["123","456"]],
    child:{
        work:'none',
        obj:{
            id:999
        }
    },
    action:function(){
        console.log(this.name);
    }
}
//封裝的遞迴方法
    function copyWid(obj){
        let newObj = Array.isArray(obj)?[]:{};
        for (var i in obj){
            if(typeof obj[i] === 'object'){  //判斷是不是物件(陣列或物件)
               newObj[i] = copyWid(obj[i])  //遞迴解決多層拷貝
            }else{
                newObj[i] = obj[i]   
            }
        }
        return newObj;
    };
    let b = copyWid(a);
    b.child.obj.id =888;
    b.skin[3][0] = "pink";
    console.log(a);
    console.log(b);

可以看到,無論是多層的物件還是多層的陣列,都能實現深拷貝,而且能拷貝函數,這個是目前來說最完美的深拷貝方法。

通過這個遞迴能實現一個比較完美的深拷貝,能彌補上述提到的所有方法中的缺點。

展開運運算元

let a = {name:'a',id:99};//如果是陣列[xx,xx,xx],{...a}需要改成[...a]
let b ={...a};  //[...a]
a.id =88;
console.log(a);
console.log(b);

這個方法能實現物件陣列單層時的深拷貝,但是多層無法實現深拷貝。

以上這麼多方法,最優解應該屬於JSON和遞迴的方法(遞迴解決了JSON無法拷貝函數方法的問題),但是根據需求資料型別的不同,可以選擇更簡單的方式。

總結

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容! 


IT145.com E-mail:sddin#qq.com