深拷貝和淺拷貝的差別?
- 如何區分深拷貝與淺拷貝,大白話來說,就是假設B複制了A,當修改A時,看B是否會發生變化,如果B也跟着變了,說明這是淺拷貝,拿人手短,如果B沒變,那就是深拷貝,自食其力。
先看一個例子:
let a = [0, 1, 2, 3, 4],
b = a;
console.log(a === b); // true
a[0] = 9;
console.log(a, b); // [9, 1, 2, 3, 4]
吆哈,明明b複制了a,為啥修改數組a,數組b也跟着變了??
基本資料類型和複雜(引用)資料類型?
基本資料類型:number, string, boolean, null, undefined, symbol
引用資料類型:Object類,有正常名值對的無須對象{a: 1}, 數組[1, 2, 3],函數等。
而這兩類資料存儲方式分别是這樣的:
- 基本類型–名值存儲在棧記憶體中,例如let a=1;
當你b=a複制時,棧記憶體會新開辟一個記憶體,例如這樣:
是以當你此時修改a=2,對b并不會造成影響,因為此時的b已自食其力,翅膀硬了,不受a的影響了。當然,let a=1, b=a; 雖然b不受a影響,但這也算不上深拷貝,因為深拷貝本身隻針對較為複雜的object類型資料。
- 引用資料類型–名存在棧記憶體中,值存在于堆記憶體中,但是棧記憶體會提供一個引用的位址指向堆記憶體中的值,我們以上面淺拷貝的例子畫個圖:
當b=a進行拷貝時,其實複制的是a的引用位址,而并非堆裡面的值。
而當我們a[0]=1時進行數組修改時,由于a與b指向的是同一個位址,是以自然b也受了影響,這就是所謂的淺拷貝了。
那,要是在堆記憶體中也開辟一個新的記憶體專門為b存放值,就像基本類型那樣,豈不就達到深拷貝的效果了。
實作簡單的深拷貝方法?
先看幾個簡單例子?
1.slice() 方法
// 先看一個js中的方法slice() 方法可從已有的數組中傳回標明的元素。
// 該方法并不會修改數組,而是傳回一個子數組。arrayObject.slice(start,end)
let a1 = [1, 2, 3, 4],
b2 = a1.slice();
a1[0] = 2;
console.log(a1, b2);
如上圖所述,slice()方法看似是一個深拷貝的方法了!在改造一下上述代碼片段,我們再看 ?
let a1 = [1, 2, [5,6], 3, 4],
b2 = a1.slice();
a1[0] = 2;
a1[2][0] = 9;
console.log(a1, b2);
拷貝的不徹底,b對象的一級屬性确實不受影響了,但是二級屬性還是沒能拷貝成功,仍然脫離不了a的控制,說明slice根本不是真正的深拷貝。
2.遞歸
// 簡單深拷貝封裝遞歸函數
function deepClone(obj) {
let objClone = Array.isArray(obj) ? [] : {};
if (obj && typeof obj === "object") {
for (key in obj) {
if (obj.hasOwnProperty(key)) { // 注:hasOwnProperty()方法
//判斷obj子元素是否為對象,如果是,遞歸複制
console.log(obj[key])
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = deepClone(obj[key]);
} else {
//如果不是,簡單複制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
let arr = [1, 2, [5, 6], 4],
b1 = deepClone(arr);
arr[2][0] = 9;
console.log(arr, b1);
// 跟之前想象的一樣,現在b脫離了a的控制,不再受a影響了。這裡再次強調,深拷貝,是拷貝對象各個層級的屬性。
注:hasOwnProperty() 官方 MDN講解方法會傳回一個布爾值,訓示對象自身屬性中是否具有指定的屬性(也就是,是否有指定的鍵)。
- 除了遞歸,我們還可以借用JSON對象的parse和stringify。
function deepClone(obj){
let _obj = JSON.stringify(obj),
objClone = JSON.parse(_obj);
return objClone
}
let a=[0,1,[2,3],4],
b=deepClone(a);
a[0]=1;
a[2][0]=1;
console.log(a,b);
4. 除了上面兩種方法之外,我們還可以借用JQ的extend方法。
$.extend( [deep ], target, object1 [, objectN ] )
deep表示是否深拷貝,為true為深拷貝,為false,則為淺拷貝
target Object類型 目标對象,其他對象的成員屬性将被附加到該對象上。
object1 objectN可選。 Object類型 第一個以及第N個被合并的對象。
let a=[0,1,[2,3],4],
b=$.extend(true,[],a);
a[0]=1;
a[2][0]=1;
console.log(a,b);