1. 程式人生 > 實用技巧 >淺拷貝與深拷貝的實現方案與應用場景

淺拷貝與深拷貝的實現方案與應用場景

寫在前面

首先我們得清楚基本概念。拷貝(Copy)即複製。

淺拷貝:建立一個新物件,儲存原始物件屬性值精準拷貝。如果屬性是基本型別,拷貝的是基本型別的值,如果屬性是引用型別,拷貝的是記憶體地址,並不會佔用新的記憶體,這種情況下如果其中一個物件改變了這個地址,會影響到另一個物件。淺拷貝只複製指向某個物件的指標,而不復制物件本身。新舊物件共享同一塊記憶體

深拷貝:將一個物件從記憶體中完整的拷貝一份出來,從堆記憶體中開闢一個新的區域存放新物件,增加了記憶體,且修改新物件不會影響原物件。新物件與原物件不共享記憶體

賦值和深/淺拷貝的區別(針對引用型別)

賦值:把一個物件賦值給一個新的變數時,賦的其實是該物件的在棧中的地址,而不是堆中的資料。

淺拷貝:重新在堆中建立記憶體,拷貝前後物件的基本資料型別互不影響,但拷貝前後物件的引用型別因共享同一塊記憶體,會相互影響。

深拷貝:從堆記憶體中開闢一個新的區域存放新物件,對物件中的子物件進行遞迴拷貝,前後的兩個物件互不影響。

淺拷貝的實現方案

0x01 Object.assign()

把任意多個源物件自身的可列舉屬性拷貝給目標物件,然後返回目標物件。

let obj2 = Object.assign({}, obj1)

0x02 函式庫lodash的_.clone方法

var _ = require('lodash');

var obj2 = _.clone(obj1);

0x03 展開運算子

同object.assign()功能相同

let obj2 = {...obj1}

0x04 Array.prototype.concat()

let arr2 = arr1.concat() // 返回新陣列,但當陣列中巢狀陣列物件時為淺拷貝

0x05 Array.prototype.slice()

let arr2 = arr1.slice() // 返回新陣列,但當陣列中巢狀陣列物件時為淺拷貝

深拷貝的實現方案

0x01 JSON.parse()和JSON.stringify()

let arr2 = JSON.parse(JSON.stringify(arr1));

缺點是不能處理函式和正則

0x02 函式庫lodash的_.cloneDeep方法

var _ = require('lodash');

var obj2 = _.cloneDeep(obj1);

0x03 jQuery.extend()方法

$.extend(deepCopy,target,obj1,[objN]) // 第一個引數為true就是深拷貝

0x04 手寫遞迴實現

解決迴圈引用的問題

function deepClone(obj, hash=new WeakMap()) {
  if(obj == null) return obj; // 不操作
  if(obj instanceof Date) return new Date(obj);
  if(obj instanceof RegExp) return new RegExp(obj);
  // 普通值/函式不需要深拷貝
  if(typeof obj !== "object") return obj;
  // 是物件的話要進行深拷貝
  if(hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所屬類原型上的constructor,而原型上的constructor指向的是當前類本身
  hash.set(obj. cloneObj);
  for(let key in obj) {
    if(obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], hash)
    }
  }
  return cloneObj;
}

深拷貝與淺拷貝的應用場景

無論是淺拷貝還是深拷貝,一般都用於操作Object 或 Array之類的複合型別。

比如想對某個陣列 或 物件的值進行修改,但是又要保留原來陣列 或 物件的值不被修改,此時就可以用深拷貝來建立一個新的陣列 或 物件,從而達到操作(修改)新的陣列 或 物件時,保留原來陣列 或 物件。

場景:從伺服器fetch到資料之後我將其存放在store中,通過props傳遞給介面,然後我需要對這堆資料進行修改,那涉及到的修改就一定有儲存和取消,所以我們需要將這堆資料拷貝到其它地方。

在JS中有一些已經封裝好的如陣列方法:concat(),filter(),slice(),map()等,在修改陣列時,不會修改原來的陣列,而是返回一個新的陣列。但這並不是真正的深拷貝,當陣列中巢狀陣列物件時仍為淺拷貝,巢狀陣列的改變仍會影響原陣列的值。