[JS系列二]談談深拷貝和淺拷貝,如何實現深拷貝

呼啦圖謎發表於2019-03-19

一、賦值

賦值是將某一數值或物件賦給某個變數的過程

  • 基本資料型別:賦值,賦值之後兩個變數互不影響
  • 引用資料型別:賦址,兩個變數具有相同的引用,指向同一個物件,相互之間有影響

對基本資料型別賦值操作,兩個變數互不影響

var a = 'Mike';
var b = a;

console.log(b);//Mike

a = "Lily";
console.log(a);//Lily
console.log(b);//Mike複製程式碼

對引用資料型別賦址操作,兩個變數指向同一個物件,改變a就會影響b

var a = {
   name : 'Mike',
   like : ['js','css']
  }
var b = a;
console.log(b);
//{
//    name:'Mike',
//    like:['js',css]
//}

a.age = 17;

console.log(b);

//{
//    name:'Mike',
//    like:['js',css],
//    age : 17
//}複製程式碼

通常在開發中並不希望改變變數a會影響到變數b,這時候就需要深拷貝和淺拷貝。

二、淺拷貝

1、什麼是淺拷貝

建立一個新物件,這個物件有著原始物件屬性值的一份精確拷貝。如果屬性是基本型別,拷貝的是基本型別值,如果是引用型別,拷貝的是記憶體地址,所以如果其中一個物件改變了地址,就會影響另一個物件

簡單來說,可以理解為淺拷貝只解決了第一層的問題


2、淺拷貝的使用場景

  • Object.assign()
  • 展開運算子
  • Array.prototype.slice()
Object.assign()
var a = {
   name : 'Mike',
   like : [
    {
        title:'js',
        bookName:'高階程式設計'
    },
    {
        title:'css',
        bookName:'css世界'
    },

   ]
};

var b = Object.assign({},a);

console.log(b);

//{
//    name:'Mike',
//    like:[
//        {
//            title:'js',
//            bookName:'高階程式設計'
//        },
//        {
//            title:'css',
//            bookName:'css世界'
//        }
//    ]
//}
a.name = 'Lily';
a.like[0].title= 'html5';
a.like[0].bookName= 'html5程式設計';
console.log(b);

//{
//    name:'Mike',
//    like:[
//        {
//            title:'html5',
//            bookName:'html5程式設計'
//        },
//        {
//            title:'css',
//            bookName:'css世界'
//        }
//    ]
//}


複製程式碼

上面程式碼說明改變a的name屬性值(基本型別)時,沒有影響到b的name屬性值,而當改變a的like屬性值(引用型別)時,b的like屬性值相應的發生變化。

展開運算子

var a = {
   name : 'Mike',
   like : [
    {
        title:'js',
        bookName:'高階程式設計'
    },
    {
        title:'css',
        bookName:'css世界'
    },

   ]
};

var b = {...a};

console.log(b);

//{
//    name:'Mike',
//    like:[
//        {
//            title:'js',
//            bookName:'高階程式設計'
//        },
//        {
//            title:'css',
//            bookName:'css世界'
//        }
//    ]
//}
a.name = 'Lily';
a.like[0].title= 'html5';
a.like[0].bookName= 'html5程式設計';
console.log(b);

//{
//    name:'Mike',
//    like:[
//        {
//            title:'html5',
//            bookName:'html5程式設計'
//        },
//        {
//            title:'css',
//            bookName:'css世界'
//        }
//    ]
//}複製程式碼

展開運算子和Object.assign()的實際效果一樣,只能拷貝第一層

Array.prototype.slice()

slice()方法返回一個新陣列物件,arr.slice([begin[, end]])

var a = [0,1,[3,4]];
var b = a.slice();
console.log(b);//[0,1,[3,4]]

a[2][0] = 5;
console.log(b);//[0,1,[5,4]]複製程式碼

當改變a[2][0]時,b也相應發生變化。

三、深拷貝

1、什麼是深拷貝

深拷貝會拷貝所有屬性,並拷貝屬性指向的動態分配記憶體。拷貝前後兩個物件互不影響。

2、深拷貝的使用場景

  • JSON.parse(JSON.stringify(object))
  • jQuery.extend()
  • lodash.cloneDeep()
var a = {
   name : 'Mike',
   like : [
    {
        title:'js',
        bookName:'高階程式設計'
    },
    {
        title:'css',
        bookName:'css世界'
    },

   ]
};

var b = JSON.parse(JSON.stringify(a));

a.name = 'Lily';
a.like[0].title= 'html5';
a.like[0].bookName= 'html5程式設計';
console.log(b);
//{
//    name:'Mike',
//    like:[
//        {
//            title:'js',
//            bookName:'高階程式設計'
//        },
//        {
//            title:'css',
//            bookName:'css世界'
//        }
//    ]
//}
console.log(a);
//{
//    name:'Lily',
//    like:[
//        {
//            title:'html5',
//            bookName:'html5程式設計'
//        },
//        {
//            title:'css',
//            bookName:'css世界'
//        }
//    ]
//}

複製程式碼
var a = [0,1,[3,4]];
var b = JSON.parse(JSON.stringify(a));
a[2][0] = 5;
console.log(a);//[0,1,[5,4]]
console.log(b);//[0,1,[3,4]]複製程式碼

以上兩段程式碼說明,通過使用JSON.parse(JSON.stringify(object)),可以實現對陣列和物件的深拷貝。

但該方法有以下幾個問題:

1、會忽略undefined

2、會忽略symbol

3、不能序列化函式

4、不能處理正則

5、不能解決迴圈引用的物件


  • 會忽略undefined 、會忽略symbol、不能序列化函式、不能處理正則
var obj = {
    a:undefined,//undefined
    b:Symbol('mike'),//Symbol()
    c:function(){},//函式
    e:/'123'/
};

var b = JSON.parse(JSON.stringify(obj));

console.log(obj);
//{
//    a:undefined,
//    b:Symbol('mike'),
//    c:function(){},
//    e:/'123'/
//}
console.log(b);
//{ e:{}}複製程式碼
  • 迴圈引用情況下會報錯
var obj = {
a:1,
b:{
     c:2
  }
};

obj.a  = obj.b;

obj.b.c = obj.a;

let b = JSON.parse(JSON.stringify(obj));

console.log(b);//VM288:13 Uncaught TypeError: Converting circular structure to JSON(…)複製程式碼

以上問題,和JSON有關。

四、如何實現深拷貝

function deep(source,hash = new WeakMap()){
    //通過使用hash來解決迴圈引用的問題
    if(hash.has(source)) return hash.get(source);
    //判斷是物件還是陣列
    var target = Array.isArray(source) ? [] : {};
    hash.set(source,target);
    for(var key in source){
        //判斷是否是自身屬性,不是繼承屬性
        if(Object.prototype.hasOwnProperty.call(source,key)){
            //判斷屬性值是否是引用型別,如果是引用型別則遞迴繼續執行該函式
            if(typeof source[key] === 'object' && source[key] !== null){
                target[key] = deep(source[key],hash)
            }else{
                target[key] = source[key];
            }
            
        }
        
    }
    return target;
}複製程式碼



相關文章