1. 程式人生 > >JavaScript淺拷貝與深拷貝

JavaScript淺拷貝與深拷貝

JavaScript中的資料型別

要理解深拷貝和淺拷貝,首先需要理解JavaScript的資料型別。可分成兩類:
1. 基本資料型別:包括Null、Undefined、Boolean、Number、String、Symbol(ES6新增)。基本型別的變數存放在棧區中,複製變數時會開闢新的棧區,將變數值複製過去,因此,複製的變數和原變數是兩個完全獨立的變數,操作不會相互影響。
2. 複雜資料型別:Object型別。變數值存放在堆區,同時會在棧區開闢空間存放指向變數值的指標。資料複製時複製的是變數指標,堆中不會再開闢空間。

深拷貝與淺拷貝

淺拷貝和深拷貝針對的是JavaScript中的複雜資料型別。

  • 淺拷貝:淺拷貝是拷貝引用,拷貝後的引用都是指向同一個物件的例項,彼此之間的操作會互相影響
  • 深拷貝:在堆中重新分配記憶體,並且把源物件所有屬性都進行深拷貝,以保證深拷貝的物件的引用圖不包含任何原有物件或物件圖上的任何物件,拷貝後的物件與原來的物件完全隔離,互不影響

淺拷貝

直接拷貝原物件的引用

通過賦值新建複雜變數的拷貝時,實際上賦值的是指向同一變數的指標。

let obj1 = { a: 0 };
let obj2 = obj1;
obj2.a = 1;
console.log(obj1.a); //1
原物件拷貝例項,但屬性物件拷貝引用

如,當用ES6的Object.assign()方法拷貝物件時,因為 Object.assign()拷貝的是屬性值。假如源物件的屬性值是一個指向物件的引用,它也只拷貝那個引用值。

let obj1 = {a:0,b:{c:1}};
let obj2 = Object.assign({},obj1);
obj2.a = 2;
obj2.b.c = 2;
console.log(obj1.a); //拷貝的是例項,0
console.log(obj1.b.c); //拷貝的是引用,2

Array物件的slice()和concat()方法實現的也是此類淺拷貝。

let obj1 = ["abc",{a:1}];
let obj2 = obj1.slice();
obj2[0] = "zxc";
obj2[1].a = 2;
console.log(obj1[0]);//abc,外層陣列拷貝的是例項
console.log(obj1[1]);//{ a: 2 },複雜資料型別的元素拷貝的是引用

深拷貝

JSON.parse(),JSON.stringify()實現深拷貝

通過JSON.stringify()現將原物件解析為字串,複製後再反解析為物件。此方法存在一定的侷限性:

  • 無法複製函式
  • 原型鏈沒了,物件就是object,所屬的類沒了。
  • let obj1 = {a:1,b:{c:1}}
    let obj2 = JSON.parse(JSON.stringify(obj1))
    obj2.a = 2
    obj2.b.c = 1
    console.log(obj1.a) //1
    console.log(obj1.b.c)//1
    遞迴實現深拷貝
    function deepCopyByRecursion(obj){
        let copyObj
        if (Object.prototype.toString.call(obj) == '[object Array]') {
            copyObj = []
        }else if(Object.prototype.toString.call(obj) == '[object Object]'){
            copyObj = {}
        }else{
            return obj
        }
    //當obj為陣列時,key代表陣列下表;當obj為物件時,key為物件屬性名。
        for (key in obj){
            copyObj[key] = deepCopyByRecursion(obj[key])
        }
        return copyObj
    }
    
    var obj1 = {a:1,b:2}
    var obj2 = deepCopyByRecursion(obj1);
    obj1.a = 0;
    obj1.b = 0;
    console.log(obj2.a)//1
    console.log(obj2.b)//2
    迭代實現深拷貝

    樹的廣度優先遍歷思想,為了閱讀方便,一下程式碼僅針對深拷貝物件的情況。

    function deepCopyByIteration(obj){
        let copyObj = {}
        let objQueue = [obj]
        let copyQueue = [copyObj]
        while(objQueue.length > 0){
            let sourceTarget = objQueue.shift()
            let copyTartget = copyQueue.shift()
            for (key in sourceTarget) {
                if (Object.prototype.toString.call(sourceTarget[key]) == '[object Object]') {
                    objQueue.push(sourceTarget[key])
                    copyTartget[key] = {}
                    copyQueue.push(copyTartget[key])
                }else{
                    copyTartget[key] = sourceTarget[key]
                }
            }
    
        }
        return copyObj
    }