深拷贝详解:特点、实现方法、循环引用处理及性能评估

news2024/12/14 20:42:36

文章目录

      • 深拷贝的特点
      • 实现深拷贝的方法
        • 1. 使用 JSON 方法
        • 2. 使用 lodash 库
        • 3. 手动实现
        • 4. 结构化克隆算法
      • 深拷贝与循环引用
      • 性能评估
      • 解决对象引用问题的其他方法
      • 手动实现深拷贝处理循环引用

深拷贝的特点

深拷贝创建了一个全新的对象,并递归复制原对象内部的所有属性和嵌套对象。这意味着新对象与原对象完全独立,修改新对象不会影响到原对象。具体特点如下:

  • 独立性:深拷贝后的新对象与其原型之间没有任何关联,即对新对象的任何操作都不会反映在原对象上。
  • 完整复制:不仅复制对象本身,还会递归地复制对象中的所有子对象、数组等复杂结构。
  • 性能开销:由于需要遍历整个对象树并为每个节点创建副本,深拷贝可能比浅拷贝消耗更多的时间和内存资源。
  • 处理复杂数据类型:能够正确处理函数、日期对象、正则表达式、MapSet 等复杂的数据类型(取决于实现方式)。
  • 循环引用支持:通过使用 WeakMapMap 来追踪已复制的对象,可以有效地处理循环引用问题,避免无限递归。

实现深拷贝的方法

1. 使用 JSON 方法

这种方法简单易用,但有明显的局限性:

  • 不能处理函数:因为函数在序列化时会被忽略。
  • 不支持特殊对象:如 DateRegExpMapSet 等对象无法被正确复制。
  • 循环引用会导致错误:JSON 序列化过程中遇到循环引用会抛出异常。
const originalObject = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(originalObject));
2. 使用 lodash 库

lodashcloneDeep 方法更加全面,能够处理更多的数据类型,并且内置了对循环引用的支持。然而,这要求项目中引入外部库。

const _ = require('lodash');
const originalObject = { a: 1, b: { c: 2 } };
const deepCopy = _.cloneDeep(originalObject);
3. 手动实现

编写递归函数来手动实现深拷贝,这种方式虽然较为繁琐,但可以自定义拷贝行为,能够处理各种特殊情况。例如:

function isObject(item) {
    return (item && typeof item === 'object' && !Array.isArray(item));
}

function deepClone(source, hash = new WeakMap()) {
    if (!isObject(source)) return source;

    // Handle circular references
    if (hash.has(source)) return hash.get(source);

    let clone = Array.isArray(source) ? [] : {};
    hash.set(source, clone);

    Object.keys(source).forEach(key => {
        if (isObject(source[key])) {
            clone[key] = deepClone(source[key], hash);
        } else {
            clone[key] = source[key];
        }
    });

    return clone;
}
4. 结构化克隆算法

某些浏览器环境(如 Web Workers API)支持结构化克隆算法,它可以处理比 JSON 方法更多的数据类型,但仍有一些限制,比如不支持函数。

深拷贝与循环引用

当对象中存在循环引用时,简单的深拷贝会导致无限递归问题。为了避免这种情况,可以在复制过程中使用 WeakMapMap 来跟踪已经复制过的对象,从而避免重复复制同一个对象。上面提供的手动实现方法就是一个很好的例子,它通过 WeakMap 来追踪已复制的对象,从而有效地解决了循环引用的问题。

性能评估

评估深拷贝的性能可以从以下几个方面考虑:

  • 时间复杂度:深拷贝的时间复杂度通常与要复制的对象结构大小成正比。对于大型或复杂的对象结构,所需的时间可能会显著增加。
  • 空间复杂度:深拷贝创建了全新的对象副本,因此它也会占用额外的内存空间。对于非常大的对象,这可能是一个重要的考虑因素。
  • 数据类型的支持:不同的深拷贝实现对不同类型的数据支持程度不同,比如函数、日期对象、正则表达式等,不恰当的处理可能会导致性能问题。
  • 循环引用的处理:处理循环引用增加了逻辑上的复杂性,可能会影响性能。使用 WeakMapMap 来追踪已复制的对象可以有效提高效率。

可以通过测试工具如 console.time()console.timeEnd() 来测量代码执行时间,也可以使用浏览器提供的性能分析工具来进行更详细的分析。此外,还可以利用基准测试框架(如 Benchmark.js)来量化性能差异。

解决对象引用问题的其他方法

除了深拷贝之外,还有多种方式可以解决对象的引用问题:

  • 浅拷贝:只复制对象的第一层属性,不递归复制内部对象。适用于不需要完整独立副本的情况。JavaScript 提供了多种浅拷贝的方式,如 Object.assign、扩展运算符 (...) 和 Array.prototype.slice
  • 不可变模式:采用不可变的数据结构或编程模式,如 React 和 Redux 中使用的那样,保证每次更新都创建新的对象而不是修改现有对象。这可以借助 Immutable.js 这样的库来简化实现。
  • 防御性拷贝:根据需求选择性地复制特定部分,而非整个对象。这样可以在保持性能的同时解决部分引用问题。
  • 值类型传递:尽可能使用简单类型(如数字、字符串),因为它们是按值传递的,不存在引用问题。这种方式适用于那些不需要复杂数据结构的场景。
  • Proxy 或 Getter/Setter:使用 JavaScript 的 Proxy 对象或自定义 getter/setter 来拦截并控制对象访问,间接管理引用问题。这种方式适合于需要细粒度控制对象访问权限或行为的场合。
  • 库辅助:使用专门设计用于管理不可变数据结构的库(如 Immutable.js)。这些库提供了高效的不可变数据操作,可以帮助开发者更容易地处理复杂的数据结构而不必担心副作用。

每种方法都有其适用场景和技术限制,选择哪种方法应根据具体需求、项目背景以及技术栈来决定。对于复杂的对象结构或需要高效处理的情况,考虑使用成熟的第三方库可能是更好的选择,因为这些库往往已经优化了性能并解决了常见的边界情况。

手动实现深拷贝处理循环引用

下面是一个更加详细的示例,展示了如何使用 WeakMap 来防止深拷贝过程中的循环引用问题。这个例子还包含了对几种常见复杂类型的处理:

function deepClone(obj, hash = new WeakMap()) {
    // 如果不是对象或者为null,直接返回
    if (obj === null || typeof obj !== 'object') return obj;

    // 如果对象已经被复制过,直接返回之前的副本
    if (hash.has(obj)) return hash.get(obj);

    // 创建一个新的实例,针对不同的构造函数做处理
    let cloneObj;
    if (obj instanceof Date) {
        cloneObj = new Date(obj);
    } else if (obj instanceof RegExp) {
        cloneObj = new RegExp(obj);
    } else if (Array.isArray(obj)) {
        cloneObj = [];
    } else {
        cloneObj = Object.create(Object.getPrototypeOf(obj));
    }

    // 将原对象和它的副本存入哈希表,防止循环引用
    hash.set(obj, cloneObj);

    // 遍历对象的每一个键值对,递归进行深拷贝
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            cloneObj[key] = deepClone(obj[key], hash);
        }
    }

    // 返回最终的副本
    return cloneObj;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2259536.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

排序算法(2):选择排序

问题 排序 [30, 24, 5, 58, 18, 36, 12, 42, 39] 选择排序 选择排序每次从待排序序列中选出最小(或最大)的元素,将其放到序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(或最大)元素…

009-jvm-对象相关的概念

#案例: 对象的创建过程 初始化默认值 成员变量显示赋值 构造代码块的初始化 构造器中的初始化 jvm

【硬件测试】基于FPGA的4FSK调制解调通信系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR

目录 1.算法仿真效果 2.算法涉及理论知识概要 3.Verilog核心程序 4.开发板使用说明和如何移植不同的开发板 5.完整算法代码文件获得 1.算法仿真效果 本文是之前写的文章: 《基于FPGA的4FSK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR》 的…

20 go语言(golang) - gin框架安装及使用(一)

一、简介 Gin是一个用Go语言编写的高性能Web框架,专注于构建快速、可靠的HTTP服务。它以其速度和简洁性而闻名,非常适合用于开发RESTful API。 高性能:Gin使用了httprouter进行路由管理,这是一个轻量级且非常快速的HTTP请求路由器…

检查读取数据寄存器输出的多扇出

为使第二寄存器被 RAM 原语吸收,来自存储器阵列的数据输出位的扇出必须为 1 。这在下图中进行了说明。 检查地址 / 读取数据寄存器上的复位信号 不应复位存储器阵列。只有 RAM 的输出可以容许复位。复位必须是同步的,以便将输出寄存器推断到 RAM 基元…

rk3588-ubuntu22.04系统网关实现路由器功能:

rk3588-ubuntu22.04系统网关实现路由器功能: 场景需求描述: 需求背景: 场景一:通过网线eth0/(路由器wlan0)访问外网: 如果网关 和 设备所处的环境可以通过网线联网或者路由器联网,那么不需要将网关配置成…

Tomcat的下载和使用,配置控制台输出中文日志

目录 1. 简介2. 下载3. 使用3.1 文件夹展示3.1.1 控制台输出乱码 3.2 访问localhost:80803.3 访问静态资源 4. 总结 1. 简介 Tomcat,全称为Apache Tomcat,是一个开源的Web应用服务器和Servlet容器,由Apache软件基金会的Jakarta项目开发。它实…

【银河麒麟高级服务器操作系统】有关dd及cp测试差异的现象分析详解

了解更多银河麒麟操作系统全新产品,请点击访问 麒麟软件产品专区:https://product.kylinos.cn 开发者专区:https://developer.kylinos.cn 文档中心:https://documentkylinos.cn dd现象 使用银河麒麟高级服务器操作系统执行两次…

【在Linux世界中追寻伟大的One Piece】自旋锁

目录 1 -> 概述 2 -> 原理 3 -> 优缺点及使用场景 3.1 -> 优点 3.2 -> 缺点 3.3 -> 使用场景 4 -> 纯软件自旋锁类似的原理实现 4.1 -> 结论 5 -> 样例代码 1 -> 概述 自旋锁是一种多线程同步机制,用于保护共享资源避免受并…

顺序表的使用,对数据的增删改查

主函数: 3.c #include "3.h"//头文件调用 SqlListptr sql_cerate()//创建顺序表函数 {SqlListptr ptr(SqlListptr)malloc(sizeof(SqlList));//在堆区申请连续的空间if(NULLptr){printf("创建失败\n");return NULL;//如果没有申请成功&#xff…

利用卷积神经网络进行手写数字的识别

数据集介绍 MNIST(Modified National Institute of Standards and Technology)数据集是一个广泛使用的手写数字识别数据集,常用于机器学习和计算机视觉领域中的分类任务。它包含了从0到9的手写数字样本,常用于训练和测试各种图像…

题解 - 取数排列

题目描述 取1到N共N个连续的数字(1≤N≤9),组成每位数不重复的所有可能的N位数,按从小到大的顺序进行编号。当输入一个编号M时,就能打印出与该编号对应的那个N位数。例如,当N=3时,可…

如何在 ASP.NET Core 3.1 应用程序中使用 Log4Net

介绍 日志记录是应用程序的核心。它对于调试和故障排除以及应用程序的流畅性非常重要。 借助日志记录,我们可以对本地系统进行端到端的可视性,而对于基于云的系统,我们只能提供一小部分可视性。您可以将日志写入磁盘或数据库中的文件&#xf…

监控易监测对象及指标之:宝兰德中间件JMX监控指标解读

监控易作为一款全面的IT监控软件,能够为企业提供深入、细致的监控服务,确保企业IT系统的稳定运行。在本文中,我们将详细解读监控易针对宝兰德中间件JMX的监控指标,以帮助用户更好地理解和应用这些监控数据。 监测指标概览&#x…

Ubuntu 安装 Samba Server

在 Mac 上如何能够与Ubuntu 服务器共享文件夹,需要在 Ubuntu 上安装 Samba 文件服务器。本文将介绍如何在 Ubuntu 上安装 Samba 服务器从而达到以下目的: Mac 与 Ubuntu 共享文件通过用户名密码访问 安装 Samba 服务 sudo apt install samba修改配置文…

数字化招聘系统如何帮助企业实现招聘效率翻倍提升?

众所周知,传统的招聘方式已经难以满足现代企业对人才的需求,而数字化招聘系统的出现,为企业提供了全新的解决方案。通过数字化招聘系统,企业可以自动化处理繁琐的招聘流程,快速筛选合适的候选人,从而大幅提…

C语言数组和字符串笔记

C语言数组和字符串笔记 1. 数组及其相关概念 1.1 为什么需要使用数组? 数组是一个有序的、类型相同的数据集合。这些数据被称为数组的元素。每个数组都有一个名字,数组名代表数组的起始地址。数组的元素通过索引或下标访问,索引从0开始。 …

u-boot移植、配置、编译学习笔记【刚开始就中止了】

教程视频地址 https://www.bilibili.com/video/BV1L24y187cK 【这个视频中途停更了…原因是实际中需要去改u-boot的情况比较少】 使用的u-boot的源码 视频中使用的是 u-boot-2017.03 学习到这里,暂停u-boot的移植、配置、编译学习,原因是经过与老师…

回归任务与分类任务应用及评价指标

能源系统中的回归任务与分类任务应用及评价指标 一、回归任务应用1.1 能源系统中的回归任务应用1.1.1 能源消耗预测1.1.2 负荷预测1.1.3 电池健康状态估计(SOH预测)1.1.4 太阳能发电量预测1.1.5 风能发电量预测 1.2 回归任务中的评价指标1.2.1 RMSE&…