淺拷貝與深拷貝的實現方案與應用場景
寫在前面
首先我們得清楚基本概念。拷貝(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()等,在修改陣列時,不會修改原來的陣列,而是返回一個新的陣列。但這並不是真正的深拷貝,當陣列中巢狀陣列物件時仍為淺拷貝,巢狀陣列的改變仍會影響原陣列的值。