JavaScript是一種非常靈活的編程語言,它具備很多特性,其中最為重要的特性之一,就是對象的引用傳遞。在JavaScript中,所有的基本數據類型都是值傳遞,即在對一個變量賦值時,將其值複製給另一個變量。而對於複雜數據類型(對象、數組等),則是採用引用傳遞的方式,即將對象的「引用」複製給另一個變量。這意味着當我們對複雜數據類型進行操作時,很可能會影響到原數據,因為這兩個變量實際上指向同一塊內存空間。因此,深拷貝與淺拷貝就成為了js編程中不可避免的問題。下面我們將從多個方面對深拷貝與淺拷貝做詳細的闡述。
一、淺拷貝
1、定義:
淺拷貝是指複製一個對象的引用,並不是複製對象本身。在JavaScript中,我們可以使用Object.assign()方法實現對象的淺拷貝。
let obj1 = { a: 0, b: { c: 0 } };
let obj2 = Object.assign({}, obj1);
console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0 } }
obj1.a = 1;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0 } }
obj2.a = 2;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 0 } }
obj2.b.c = 3;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 3 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 3 } }
由上面的代碼可以看出,使用Object.assign()方法可以實現對象的淺拷貝,當只是拷貝了第一層的數據時,obj1和obj2並不會互相影響。但是,當拷貝了第二層及其以上層次時,obj1和obj2就會產生影響,因為對象的屬性b被拷貝的時候,並沒有複製其引用指向的內存空間,而是複製了其引用值,這就導致obj1和obj2的屬性b所指向的內存空間是相同的。因此,當我們修改屬性b的值時,兩個變量會產生相同的變化,這就是典型的淺拷貝問題。
2、淺拷貝的應用場景:
淺拷貝適用於不包含引用類型數據的對象或者數組。
二、深拷貝
1、定義:
深拷貝是指對一個對象的完全拷貝,即便這個對象的屬性值也是引用類型,也能夠遞歸地拷貝子對象的所有屬性。在JavaScript中,我們可以使用JSON.parse(JSON.stringify(obj))方式實現對象的深拷貝。
let obj1 = { a: 0, b: { c: 0 } };
let obj2 = JSON.parse(JSON.stringify(obj1));
console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0 } }
obj1.a = 1;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0 } }
obj2.a = 2;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 0 } }
obj2.b.c = 3;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 3 } }
使用JSON.parse(JSON.stringify(obj))方式可以實現對象的深拷貝,即使對象的屬性值也是引用類型,也能夠遞歸地拷貝子對象的所有屬性。由此可見,深拷貝可以真正意義上地將兩個對象相互獨立開來,互不影響。
2、深拷貝的應用場景:
深拷貝適用於包含引用類型數據的對象或者數組。
三、深淺拷貝的實現方式
1、基於JSON.stringify()和JSON.parse()的深拷貝實現:
function deepClone_Json(obj) {
let str = JSON.stringify(obj);
return JSON.parse(str);
}
let obj1 = { a: 0, b: { c: 0 } };
let obj2 = deepClone_Json(obj1);
obj1.a = 1;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0 } }
obj2.a = 2;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 0 } }
obj2.b.c = 3;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 3 } }
通過重寫JSON.stringify()和JSON.parse()方法可以實現深拷貝,但是這種方法存在以下幾個問題:
- 該方法無法拷貝函數,RegExp對象等特殊對象。
- 在部分特殊場景下,該方法可能會拋出異常。
- 該方法無法解決循環引用的問題。
2、遞歸方式的深拷貝實現:
function deepClone_Recursion(obj) {
if (typeof obj !== "object" || obj === null) {
return obj;
}
let newObj = obj instanceof Array ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepClone_Recursion(obj[key]);
}
}
return newObj;
}
let obj1 = { a: 0, b: { c: 0 } };
let obj2 = deepClone_Recursion(obj1);
obj1.a = 1;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0 } }
obj2.a = 2;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 0 } }
obj2.b.c = 3;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 } }
console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 3 } }
通過遞歸的方式實現深拷貝,可以解決前面所提到的問題,但是這種方法在處理循環引用的時候,難免會陷入死循環,導致瀏覽器崩潰。
3、利用WeakMap解決循環引用的深拷貝實現:
function deepClone(obj, map = new WeakMap()) {
if (typeof obj !== "object" || obj === null) {
return obj;
}
if (map.has(obj)) {
return map.get(obj);
}
let newObj = obj instanceof Array ? [] : {};
map.set(obj, newObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepClone(obj[key], map);
}
}
return newObj;
}
let obj1 = { a: 0, b: { c: 0 } };
let obj2 = { a: obj1, b: obj1 };
obj1.c = obj2;
let obj3 = deepClone(obj1);
obj1.a = 1;
console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0 }, "c": { "a": {...}, "b": {...} } }
console.log(JSON.stringify(obj3)); // { "a": 0, "b": { "c": 0 }, "c": { "a": {...}, "b": {...} } }
利用WeakMap來解決循環引用問題的深拷貝實現方式是目前最優的解決方案之一。WeakMap是ES6新增的一種Map類型,與Map類型相比,WeakMap只能用對象作為鍵,當對象被垃圾回收機制回收時,相應的鍵值對也會從WeakMap中刪除,從而避免內存泄漏。通過在遞歸過程中維護一個map,我們可以記錄已經拷貝過的對象,避免循環引用陷入死循環。
四、總結
深拷貝與淺拷貝是JavaScript編程中不可避免的問題,尤其是在處理對象和數組時尤為突出。深淺拷貝的實現方式也有多種,每種方式都有其優缺點。在實際開發中,我們需要根據具體場景來選擇最適合的拷貝方式,避免因為深淺拷貝問題導致程序出現不可預期的錯誤。
原創文章,作者:DTANP,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/334262.html