JavaScript的深拷貝淺拷貝詳解

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-tw/n/334262.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
DTANP的頭像DTANP
上一篇 2025-02-05 13:05
下一篇 2025-02-05 13:05

相關推薦

  • 使用JavaScript日期函數掌握時間

    在本文中,我們將深入探討JavaScript日期函數,並且從多個視角介紹其應用方法和重要性。 一、日期的基本表示與獲取 在JavaScript中,使用Date對象來表示日期和時間,…

    編程 2025-04-28
  • JavaScript中使用new Date轉換為YYYYMMDD格式

    在JavaScript中,我們通常會使用Date對象來表示日期和時間。當我們需要在網站上顯示日期時,很多情況下需要將Date對象轉換成YYYYMMDD格式的字元串。下面我們來詳細了…

    編程 2025-04-27
  • JavaScript中修改style屬性的方法和技巧

    一、基本概念和方法 style屬性是JavaScript中一個非常重要的屬性,它可以用來控制HTML元素的樣式,包括顏色、大小、字體等等。這裡介紹一些常用的方法: 1、通過Java…

    編程 2025-04-25
  • Linux sync詳解

    一、sync概述 sync是Linux中一個非常重要的命令,它可以將文件系統緩存中的內容,強制寫入磁碟中。在執行sync之前,所有的文件系統更新將不會立即寫入磁碟,而是先緩存在內存…

    編程 2025-04-25
  • 神經網路代碼詳解

    神經網路作為一種人工智慧技術,被廣泛應用於語音識別、圖像識別、自然語言處理等領域。而神經網路的模型編寫,離不開代碼。本文將從多個方面詳細闡述神經網路模型編寫的代碼技術。 一、神經網…

    編程 2025-04-25
  • git config user.name的詳解

    一、為什麼要使用git config user.name? git是一個非常流行的分散式版本控制系統,很多程序員都會用到它。在使用git commit提交代碼時,需要記錄commi…

    編程 2025-04-25
  • MPU6050工作原理詳解

    一、什麼是MPU6050 MPU6050是一種六軸慣性感測器,能夠同時測量加速度和角速度。它由三個感測器組成:一個三軸加速度計和一個三軸陀螺儀。這個組合提供了非常精細的姿態解算,其…

    編程 2025-04-25
  • Java BigDecimal 精度詳解

    一、基礎概念 Java BigDecimal 是一個用於高精度計算的類。普通的 double 或 float 類型只能精確表示有限的數字,而對於需要高精度計算的場景,BigDeci…

    編程 2025-04-25
  • 詳解eclipse設置

    一、安裝與基礎設置 1、下載eclipse並進行安裝。 2、打開eclipse,選擇對應的工作空間路徑。 File -> Switch Workspace -> [選擇…

    編程 2025-04-25
  • Python安裝OS庫詳解

    一、OS簡介 OS庫是Python標準庫的一部分,它提供了跨平台的操作系統功能,使得Python可以進行文件操作、進程管理、環境變數讀取等系統級操作。 OS庫中包含了大量的文件和目…

    編程 2025-04-25

發表回復

登錄後才能評論