JavaScript 淺拷貝和深拷貝

来源:https://www.cnblogs.com/yuzhihui/archive/2023/01/16/17055138.html
-Advertisement-
Play Games

JavaScript 中的拷貝分為兩種:淺拷貝和深拷貝。 淺拷貝是指在拷貝過程中,只拷貝一個對象中的指針,而不拷貝實際的數據。所以,淺拷貝中修改新對象中的數據時,原對象中的數據也會被改變。 深拷貝是指在拷貝過程中,拷貝一個對象中的所有數據,並創建一個新對象,對新對象進行操作並不會影響到原對象。 ...


JavaScript 中的拷貝分為兩種:淺拷貝和深拷貝。

一、淺拷貝

淺拷貝是指在拷貝過程中,只拷貝一個對象中的指針,而不拷貝實際的數據。所以,淺拷貝中修改新對象中的數據時,原對象中的數據也會被改變。

JavaScript 中淺拷貝可以通過如下幾種方式實現:

  • 使用結構賦值的方式,例如 let newObject = {...oldObject}
  • 使用 Object.assign() 方法,例如 let newObject = Object.assign({}, oldObject)

二、深拷貝

深拷貝是指在拷貝過程中,拷貝一個對象中的所有數據,並創建一個新對象,對新對象進行操作並不會影響到原對象。

1、常規場景

JavaScript 中深拷貝可以通過如下幾種方式實現:

  • 使用 JSON.parse(JSON.stringify(object)) 方法

需要註意的是:該方法會忽略 undefined 以及正則表達式類型的屬性。

const A = { a: 7788, b: undefined, c: new RegExp(/-/ig) },
    B = JSON.parse(JSON.stringify(A));

console.log('A', A);
console.log('B', B);

  • 使用遞歸的方式,手動拷貝對象的每一層
function deepCopy(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    let copy;
    if (Array.isArray(obj)) {
        copy = [];
        for (let i = 0; i < obj.length; i++) {
            copy[i] = deepCopy(obj[i]);
        }
    } else {
        copy = {};
        for (let key in obj) {
            copy[key] = deepCopy(obj[key]);
        }
    }
    return copy;
}

const objA = { a: 123 },
    objB = { b: 456 };

// 淺拷貝
const objC = {...objA};
console.log('objA.a', objA.a);  // objA.a 123
console.log('objC.a', objA.a);  // objC.a 123
objC.a = 788;
console.log('objA.a', objA.a);  // objA.a 788
console.log('objC.a', objC.a);  // objC.a 788

// 深拷貝
const objD = deepCopy(objB);
console.log('objB.b', objB.b);  // objB.b 456
console.log('objD.b', objD.b);  // objD.b 456
objD.b = 899;
console.log('objB.b', objB.b);  // objB.b 456
console.log('objD.b', objD.b);  // objD.b 899

這個函數接受一個參數 obj,如果它不是對象或者是 null,那麼直接返回該參數。如果它是數組,則創建一個新數組並遞歸複製每一項。否則,創建一個新對象並遞歸複製每一個屬性。

  • 使用 lodash 類庫的_.cloneDeep函數、 underscore 中的 _.clone() 函數等第三方庫

2、特定場景一:內置對象類型的深拷貝

JavaScript 中複製內置對象類型(例如 Date,RegExp 等)的深拷貝可以使用特定的構造函數來重新創建該對象。

例如,對於 Date 對象,可以使用 new Date(originalDate.getTime()) 來創建一個新的日期對象,其中 originalDate.getTime() 返回原始日期對象的時間戳。

對於 RegExp 對象,可以使用 new RegExp(originalRegExp) 或 new RegExp(originalRegExp.source, originalRegExp.flags) 來創建一個新的正則表達式對象。

下麵是一個使用構造函數來複制內置對象類型的深拷貝示例:


function deepCopy(obj) {
    let copiedObjects = new WeakMap();
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    if (copiedObjects.has(obj)) {
        return copiedObjects.get(obj);
    }
    let copy;
    if (obj instanceof Date) {
        copy = new Date(obj.getTime());
    } else if (obj instanceof RegExp) {
        copy = new RegExp(obj);
    } else if (Array.isArray(obj)) {
        copy = [];
    } else {
        copy = {};
    }
    copiedObjects.set(obj, copy);
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] === 'object' && obj[key] !== null) {
                copy[key] = deepCopy(obj[key], copiedObjects);
            } else {
                copy[key] = obj[key];
            }
        }
    }
    return copy;
}

這個示例的深拷貝函數首先檢查當前對象是否為內置對象類型,如果是,則使用相應的構造函數重新創建該對象,否則創建一個普通對象或數組。然後進行遞歸複製每一個屬性。

需要註意的是,使用構造函數複製內置對象類型只適用於部分內置對象類型,對於其他的內置對象類型,可能需要使用其他的方法來進行複製,或者使用第三方庫來進行複製。

總之,深拷貝複製內置對象類型需要考慮使用構造函數來重新創建對象,如果需要對這些對象進行深拷貝操作,可以使用上述方法或其他庫來實現。

3、特定場景二:自定義對象類型的深拷貝

JavaScript 中自定義對象的深拷貝可以使用同樣的遞歸方式實現,可以使用 WeakMap 方法。

function deepCopy(obj) {
    let copiedObjects = new WeakMap();
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    if (copiedObjects.has(obj)) {
        return copiedObjects.get(obj);
    }
    let copy;
    if (obj instanceof MyCustomObject) {
        copy = new MyCustomObject();
    } else if (Array.isArray(obj)) {
        copy = [];
    } else {
        copy = {};
    }
    copiedObjects.set(obj, copy);
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] === 'object' && obj[key] !== null) {
                copy[key] = deepCopy(obj[key], copiedObjects);
            } else {
                copy[key] = obj[key];
            }
        }
    }
    return copy;
}

這個函數首先檢查當前對象是否為自定義對象,如果是,則創建一個新的自定義對象,否則創建一個普通對象或數組。然後進行遞歸複製每一個屬性。

註意,如果自定義對象中包含迴圈引用,需要使用 WeakMap 來避免出現死迴圈。

4、特定場景三:對象中存在函數或迴圈引用

對於函數,通常會忽略它們,因為函數不能被覆制,而是需要重新定義。

對於迴圈引用,可以使用 WeakMap 來存儲已經複製過的對象。每次遇到迴圈引用時,可以檢查 WeakMap 中是否已經有該對象的副本,如果有,則直接使用副本,而不是重新創建。

function deepCopy(obj) {
    let copiedObjects = new WeakMap();
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    if (copiedObjects.has(obj)) {
        return copiedObjects.get(obj);
    }
    let copy;
    if (Array.isArray(obj)) {
        copy = [];
    } else {
        copy = {};
    }
    copiedObjects.set(obj, copy);
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] === 'object' && obj[key] !== null) {
                copy[key] = deepCopy(obj[key], copiedObjects);
            } else {
                copy[key] = obj[key];
            }
        }
    }
    return copy;
}

這是使用 WeakMap 的一種示例,這個示例的深拷貝函數遞歸地複製對象,但檢查 WeakMap 中是否已經存在該對象的副本,如果存在則直接使用副本,而不是重新創建。

此外,使用 JSON.parse(JSON.stringify(obj)) 方法會自動忽略函數和迴圈引用,但是會忽略 undefined 以及正則表達式類型的屬性。

還有,還可以使用第三方庫,如 lodash 中的 _.cloneDeep() 函數、 underscore 中的 _.clone() 函數等來實現對象中存在函數或迴圈引用的深拷貝。

5、特定場景四:對象中有對其他對象的引用或者包含 Symbol 屬性的對象

對於對象中有對其他對象的引用,可以使用 WeakMap 來存儲已經複製過的對象。每次遇到對其他對象的引用時,可以檢查 WeakMap 中是否已經有該對象的副本,如果有,則直接使用副本,而不是重新創建。

對於對象中包含 Symbol 屬性的對象,可以使用 Object.getOwnPropertySymbols() 方法來獲取該對象所有的 Symbol 屬性,然後使用 Object.getOwnPropertyDescriptor() 方法來獲取這些 Symbol 屬性的值,最後將這些值賦給新對象。


function deepCopy(obj) {
    let copiedObjects = new WeakMap();
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    if (copiedObjects.has(obj)) {
        return copiedObjects.get(obj);
    }
    let copy;
    if (Array.isArray(obj)) {
        copy = [];
    } else {
        copy = {};
    }
    copiedObjects.set(obj, copy);
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] === 'object' && obj[key] !== null) {
                copy[key] = deepCopy(obj[key], copiedObjects);
            } else {
                copy[key] = obj[key];
            }
        }
    }
    let symbols = Object.getOwnPropertySymbols(obj);
    symbols.forEach(symbol => {
        let descriptor = Object.getOwnPropertyDescriptor(obj, symbol);
        Object.defineProperty(copy, symbol, descriptor);
    });
    return copy;
}

這是使用 WeakMap 和 Symbol 屬性的一種示例,這個示例的深拷貝函數首先檢查 WeakMap 中是否已經存在該對象的副本,如果存在則直接使用副本,而不是重新創建。然後使用 Object.getOwnPropertySymbols() 方法獲取該對象所有的 Symbol 屬性,最後使用 Object.getOwnPropertyDescriptor() 方法獲取這些 Symbol 屬性的值,並將這些值賦給新對象。

這種方法可以保證深拷貝對象中包含的所有屬性,包括對其他對象的引用和 Symbol 屬性,但還是不能複製內置對象類型,這些對象類型是不可枚舉的。

三、迴圈引用

JavaScript 中的迴圈引用指的是兩個或多個對象之間相互引用的情況。這種情況通常發生在將一個對象賦給另一個對象的屬性時,同時還將另一個對象賦給第一個對象的屬性。

以下是一個示例:

let obj1 = {};
let obj2 = {};

obj1.prop = obj2;
obj2.prop = obj1;

這樣就會產生一個迴圈引用,因為 obj1 和 obj2 相互引用。

迴圈引用可能導致 JavaScript 引擎無法正確處理記憶體,並導致記憶體泄漏。因此,在編寫 JavaScript 代碼時需要特別註意避免迴圈引用。如果您需要在兩個對象之間建立關係,可以使用弱引用來避免迴圈引用。

在深拷貝中遇到迴圈引用就會導致死迴圈,因此需要使用特殊的演算法來解決這個問題。可以使用遞歸演算法和深度優先遍歷來實現深拷貝,在遍歷過程中跟蹤已經遍歷過的對象,如果遇到迴圈引用就直接返回已經遍歷過的對象的引用。

 

總的來說,在使用淺拷貝和深拷貝時,需要根據需求和對象的結構來進行選擇。通常來說,如果需要對對象進行修改並且不希望對原對象造成影響,那麼應該使用深拷貝。如果只是需要讀取對象中的數據而不需要修改,那麼可以使用淺拷貝。在實現深拷貝時,需要特別註意迴圈引用和特殊屬性問題。

作者:yuzhihui
出處:http://www.cnblogs.com/yuzhihui/ 聲明:歡迎任何形式的轉載,但請務必註明出處!!!
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Elasticsearch(簡稱:ES)功能強大,其背後有很多預設值,或者預設操作。這些操作優劣並存,優勢在於我們可以迅速上手使用 ES,劣勢在於,其實這些預設值的背後涉及到很多底層原理,怎麼做更合適,只有數據使用者知道。用 ES 的話來說,你比 ES 更懂你的數據,但一些配置信息、限制信息,還是需... ...
  • SummingMergeTree引擎繼承自MergeTree。區別在於,當合併SummingMergeTree表的數據片段時,ClickHouse會把所有具有相同主鍵的行合併為一行,該行包含了被合併的行中具有數值數據類型的列的彙總值。如果主鍵的組合方式使得單個鍵值對應於大量的行,則可以顯著的減少存儲 ...
  • 用正則表達式進行搜索 正則表達式介紹 正則表達式是用來匹配文本的特殊的串(字元集合)。 使用MySQL正則表達式 MySQL用WHERE子句對正則表達式提供了初步的支持,允許你指定正則表達式,過濾SELECT檢索出的數據。MySQL僅支持多數正則表達式實現的一個很小的子集。 基本字元匹配 SELEC ...
  • 1. MySQL8安裝 安裝環境 操作系統:CentOS7 MySQL版本:8.0.28 安裝方式:二進位Generic 軟體路徑:/app/database 數據路徑:/data/3306 日誌路徑:/binlog/3306 MySQL Community Server 社區版官網下載鏈接 MyS ...
  • 摘要:華為日曆月活高達數千萬,這使其對支撐業務的資料庫提出了巨大挑戰:高併發場景下,資料庫如何實現快速擴容?海量數據運行,如何確保業務穩定性? 本文分享自華為雲社區《穩定支撐千萬級月活,華為日曆背後的英雄》,作者: GaussDB 資料庫。 隨著科技進步,手機日曆早已融入我們的生活,不僅可以記錄時間 ...
  • 列表中自動播放視頻,常規方案是在每個 xml 中寫入視頻佈局,然後在滑動時獲取當前的下標,播放此下標的視頻 弊端:播放容易出錯,需要精準控制好停止播放操作,並且適配器中容易觸發多次刷新,導致執行多次同樣的操作,不易控制離開停止等操作,增加了佈局的負擔,影響滑動流暢度,無法復用... 使用過的都比較清 ...
  • 前言 最近在做博客園的界面美化,用的是博客園[guangzan]的開源項目,配置超級簡單,只需要複製粘貼代碼就好啦。 但在粘貼 CSS 代碼時遇到一個問題,那就是所有代碼都擠在了一行,沒有一點排板的樣子(如下圖),對我來說是不能忍受的,便決定解決這一問題。 經過一番網上衝浪後,我找到瞭解決方法,並且 ...
  • JavaScript 中有多種方法來判斷變數的類型,如 typeof、instanceof、Object.prototype.toString.call()、constructor屬性、Symbol.toStringTag屬性以及 lodash 等第三方庫 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...