1. 程式人生 > >總結JavaScript物件的深淺拷貝

總結JavaScript物件的深淺拷貝

十四、物件的淺拷貝與深拷貝

  • 什麼是物件的拷貝?
    將一個物件賦值給另外一個物件, 我們稱之為物件的拷貝

  • 什麼是深拷貝, 什麼是淺拷貝?
    我們假設將A物件賦值給B物件

  • 淺拷貝是指, 修改B物件的屬性和方法會影響到A物件的屬性和方法, 我們稱之為淺拷貝

    以下兩種情況都屬於淺拷貝:

    1、預設情況下物件之間的直接賦值都是淺拷貝

     let A = {
          name: 'zyx',
          age: 20
        }
     let B = A
     console.log(B) // {name: "zyx", age: 20}
     //修改B的 name 屬性
     B.name = 'ls'
     //A 也收到影響
     console.log(A) // {name: "ls", age: 20}
     console.log(B) // {name: "ls", age: 20}

    ​ 賦值操作(包括物件作為引數、返回值),不會開闢新的記憶體空間,他只是賦值了物件的引用.也就是除了B這個名字之外,沒有其他的記憶體開銷,修改了A也就影響了B,修改了B,也就影響了A.

    如圖所示:

2、如果物件的屬性包含了引用資料型別(陣列、物件),那麼哪怕不是直接賦值操作,而是開闢了一層新的記憶體空間,也就是說只拷貝了A物件的一層,這仍然屬於淺拷貝。

 let A = {
      name: 'ls',
      age: 20,
      hobbies: ['dance','basketball','read'],
      dogs:{
       name: '大黃',
       color: 'yellow' 
     }
    }
 let B = {}
 //定義一個函式,把A物件的屬性複製一份給B
 function extend(obj1,obj2){
     for(var key in obj1){
         obj2[key] = obj1[key]
     }
 }
 extend(A,B)
//修改B物件中的引用型別資料 ,A物件也收到影響
B.dogs.color = 'red'
B.hobbies[0] = 'sing'
console.log(B) 
console.log(A) 

執行截圖如下:修改B物件中的引用型別資料 ,A物件也收到影響,屬於淺拷貝

3、ES6中新增的 Object.assign() 也是物件的淺拷貝

Object.assign方法用於物件的合併,將源物件(source)的所有可列舉屬性,複製到目標物件(target)。 Object.assign方法的第一個引數是目標物件,後面的引數都是源物件。 注意,如果目標物件與源物件有同名屬性,或多個源物件有同名屬性,則後面的屬性會覆蓋前面的屬性。

const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2

上面程式碼中,源物件obj1a屬性的值是一個物件,Object.assign拷貝得到的是這個物件的引用。這個物件的任何變化,都會反映到目標物件上面。

4、擴充套件運算子(...)

利用擴充套件運算子可以在構造字面量物件時,進行克隆或者屬性拷貝 ,屬於淺拷貝

var obj = {a:1,b:{c:1}}
var obj2 = {...obj};
obj.a=2;
console.log(obj); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}

obj.b.c = 2;
console.log(obj); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}

5、Array.prototype.slice()

slice() 方法返回一個新的陣列物件,這一物件是一個由 begin和 end(不包括end)決定的原陣列的淺拷貝。原始陣列不會被改變。

  • 深拷貝是指, 修改B物件的屬性和方法不會影響到A物件的屬性和方法, 我們稱之為深拷貝

    以下兩種情況都屬於深拷貝:

    1、預設情況下一個物件的屬性如果是基本資料型別, 那麼進行復制(拷貝),都是深拷貝

    如果A物件的屬性都是基本資料型別(Number、String等),此時要想深拷貝一份A給B,該怎麼做呢,在這種要拷貝的物件A只有基本型別的資料時,只需要在記憶體中開闢一塊空間儲存B就行了。

     let A = {
          name: 'zyx',
          age: 20
        }
     let B = {}
     //定義一個函式,把A物件的屬性複製一份給B
     function extend(obj1,obj2){
         for(var key in obj1){
             obj2[key] = obj1[key]
         }
     }
     extend(A,B)
    console.log(B) // {name: "zyx", age: 20}
    B.name = 'ls'
    console.log(B) // {name: "ls", age: 20}
    console.log(A) // {name: "zyx", age: 20}

    這樣就實現了深拷貝,如下圖所示:

2、如果要拷貝的物件本身又包含了引用資料型別,即物件又包含陣列或者物件,層層巢狀的情況下,想要實現物件的深拷貝,可以採用遞迴的方式進行深拷貝。

 let A = {
    name: 'ls',
    age: 20,
    hobbies: ['dance','basketball','read'],
    dogs:{
      name: '大黃',
      color: 'yellow'
    }
  }
  let B = {}
  //定義一個函式,把A物件的屬性複製一份給B
  function extend(obj1,obj2){
    for(var key in obj1){
      var item = obj1[key]
      if(item instanceof Array){
        obj2[key] = []
        extend(item,obj2[key])
      }else if(item instanceof Object){
        obj2[key] = {}
        extend(item,obj2[key])
      }else{
        obj2[key] = item
      }
    }
  }
  extend(A,B)

  B.dogs.color = 'red'
  B.hobbies[0] = 'sing'
  console.log(B)
  console.log(A)

執行發現,修改B物件的引用資料型別,不會影響到A物件,完成深拷貝

我們可以對深拷貝的程式碼進行封裝優化

function deepClone(obj){
    let cloneObj = {}
    for(let key in obj){
        if(typeof obj[key] === 'object'){
            cloneObj[key] = deepClone(obj[key])
        }else{
            cloneObj[key] = obj[key]
        } 
    }
    return cloneObj
}

3、通過JSON.stringify實現深拷貝

JSON.stringify()是目前前端開發過程中最常用的深拷貝方式,原理是把一個物件序列化成為一個JSON字串,將物件的內容轉換成字串的形式再儲存在磁碟上,再用JSON.parse()反序列化將JSON字串變成一個新的物件。

var obj1 = {
    a:1,
    b:[1,2,3]
}
var str = JSON.stringify(obj1)
var obj2 = JSON.parse(str)
console.log(obj2); //{a:1,b:[1,2,3]}
obj1.a=2
obj1.b.push(4);
console.log(obj1); //{a:2,b:[1,2,3,4]}
console.log(obj2); //{a:1,b:[1,2,3]}