手写深拷贝(Deep Copy)和浅拷贝(Shallow Copy)

在编程中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是两个重要的概念,特别是在处理对象或数组时。它们的主要区别在于如何处理对象或数组中的引用类型(如对象、数组等)。

浅拷贝(Shallow Copy)

浅拷贝会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

在JavaScript中,我们可以使用Object.assign()方法或展开运算符(...)来进行浅拷贝。

使用Object.assign()进行浅拷贝

let obj1 = {
  a: 1,
  b: { c: 2 }
};

let obj2 = Object.assign({}, obj1);

obj2.b.c = 3;

console.log(obj1); // { a: 1, b: { c: 3 } }
console.log(obj2); // { a: 1, b: { c: 3 } }

深拷贝(Deep Copy)

深拷贝会创建一个新的对象,并递归地复制对象的所有属性及其子对象,直到它们都是基本类型为止。这样,新对象与原始对象没有任何关联。

在JavaScript中,没有内置的函数可以直接进行深拷贝,但我们可以使用JSON方法(但这种方法有局限性,比如不能处理函数和循环引用)或者手动实现一个深拷贝函数。

使用JSON方法进行深拷贝(有局限性)

let obj1 = {
  a: 1,
  b: { c: 2 }
};

let obj2 = JSON.parse(JSON.stringify(obj1));

obj2.b.c = 3;

console.log(obj1); // { a: 1, b: { c: 2 } }
console.log(obj2); // { a: 1, b: { c: 3 } }

JSON.stringify()JSON.parse() 的上下文中,不能处理函数和循环引用指的是:

  1. 函数(Functions):
    当您尝试使用 JSON.stringify() 将一个包含函数的 JavaScript 对象转换为 JSON 字符串时,该函数将不会被转换为字符串的一部分。在 JSON 规范中,函数不是有效的数据类型,因此它们会被忽略。如果您在对象中有一个函数属性,并且您尝试将其转换为 JSON 字符串,那么这个函数属性将不会出现在生成的字符串中。
    例如:
const obj = {
    name: "John",
    greet: function() {
        console.log("Hello, " + this.name);
    }
};

const jsonString = JSON.stringify(obj);
console.log(jsonString); // 输出: {"name":"John"}


如上所示,greet 函数没有出现在输出的 JSON 字符串中。

  1. 循环引用(Circular References):
    在 JavaScript 中,对象可以通过属性相互引用,形成循环引用。当您尝试使用 JSON.stringify() 转换包含循环引用的对象时,它会抛出一个错误,因为 JSON 格式不支持循环引用。
    例如:
const obj1 = {};
const obj2 = { ref: obj1 };
obj1.alsoRef = obj2;

try {
    const jsonString = JSON.stringify(obj1);
} catch (error) {
    console.error(error); // 抛出错误,因为存在循环引用
}


在这个例子中,obj1obj2 通过 refalsoRef 属性相互引用,形成了一个循环。当您尝试使用 JSON.stringify() 转换这个对象时,它会抛出一个错误。
为了避免循环引用的问题,您可以使用一个 replacer 函数来排除或处理循环引用。但是,请注意,即使您使用 replacer 函数,您也无法在 JSON 字符串中保留循环引用的结构,因为 JSON 格式本身不支持这种结构。
对于 JSON.parse() 来说,它不会遇到循环引用的问题,因为它只是将有效的 JSON 字符串转换回 JavaScript 对象。但是,如果您从某个源(如服务器)接收到包含无效循环引用的 JSON 字符串,并且尝试使用 JSON.parse() 解析它,那么它将抛出一个 SyntaxError。在正常的 JSON 字符串中,您不会遇到循环引用,因为 JSON 格式不支持它们。


手动实现深拷贝(递归方法)

function deepCopy(obj, hash = new WeakMap()) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }

  if (hash.has(obj)) {
    return hash.get(obj);
  }

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

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = deepCopy(obj[key], hash);
    }
  }

  return copy;
}

let obj1 = {
  a: 1,
  b: { c: 2 }
};

let obj2 = deepCopy(obj1);

obj2.b.c = 3;

console.log(obj1); // { a: 1, b: { c: 2 } }
console.log(obj2); // { a: 1, b: { c: 3 } }

这个手动实现的深拷贝函数可以处理对象和数组,并且可以处理循环引用。它使用了一个WeakMap来存储已经拷贝过的对象,以便在遇到循环引用时能够返回正确的拷贝。

手写深拷贝

deepClone 函数是一个实现了深拷贝功能的函数,它递归地遍历对象并复制其属性,包括数组和嵌套对象。同时,添加了递归深度的打印以及属性的克隆过程,这有助于理解函数是如何工作的。

简要分析:

  1. deepClone 函数接受两个参数:obj(要克隆的对象)和 depth(当前递归的深度,默认为0)。
  2. 使用 console.log 打印当前递归的深度和被克隆的对象,这对于调试和理解函数执行过程非常有用。
  3. 检查 obj 是否不是对象或是否为 null,如果是,则直接返回 obj 本身(基本数据类型和 null 的值传递)。
  4. 初始化 result 变量,根据 obj 的类型(数组或对象)来创建一个新的空数组或空对象。
  5. 使用 for...in 循环遍历 obj 的所有可枚举属性。
  6. 使用 hasOwnProperty 方法检查属性是否是 obj 自身的属性(而不是继承自原型链的属性)。
  7. 对于每个属性,递归调用 deepClone 函数来克隆属性的值,并将结果存储在新的 result 对象中。
  8. 返回 result,即克隆后的对象。

在示例使用部分,您创建了一个包含各种类型属性的对象 original,并使用 deepClone 函数克隆了它。然后,您打印了原始对象和克隆后的对象,以验证深拷贝是否成功。

执行这段代码,您应该会在控制台看到递归深度和对象被克隆的过程,以及原始对象和克隆后的对象的内容。由于 deepClone 函数实现了深拷贝,所以原始对象和克隆后的对象在内存中是独立的,修改其中一个不会影响另一个。

基础版本

会写基础版本的就够了,对于有工作经验的人还要考虑Map,Sety以及循环引用

function deepClone(obj, depth = 0) {
    console.log('depth:', depth, 'value:', obj);
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    let result;
    if (Array.isArray(obj)) {
        result = [];
    } else {
        result = {};
    }
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            const value = obj[key];
            console.log(`Cloning property ${key}`)
            result[key] = deepClone(value, depth + 1)
        }
    }
    return result;
}
// 示例使用
const original = {
    number: 1,
    bool: true,
    str: 'string',
    array: [1, 2, 3],
    obj: { child: 'child', father: { child_1: 'father_1' } }
};

const cloned = deepClone(original);

//   console.log('Original:', original);
console.log('Cloned:', cloned);
PS D:\练\js\手写\13-深拷贝> node .\lian.js\
depth: 0 value: {
  number: 1,
  bool: true,
  str: 'string',
  array: [ 1, 2, 3 ],
  obj: { child: 'child', father: { child_1: 'father_1' } }
}
Cloning property number
depth: 1 value: 1
Cloning property bool
depth: 1 value: true
Cloning property str
depth: 1 value: string
Cloning property array
depth: 1 value: [ 1, 2, 3 ]
Cloning property 0
depth: 2 value: 1
Cloning property 1
depth: 2 value: 2
Cloning property 2
depth: 2 value: 3
Cloning property obj
depth: 1 value: { child: 'child', father: { child_1: 'father_1' } }
Cloning property child
depth: 2 value: child
Cloning property father
depth: 2 value: { child_1: 'father_1' }
Cloning property child_1
depth: 3 value: father_1
Cloned: {
  number: 1,
  bool: true,
  str: 'string',
  array: [ 1, 2, 3 ],
  obj: { child: 'child', father: { child_1: 'father_1' } }
}

提供的 deepClone 函数虽然能够处理大部分基本的深拷贝场景,但它确实有一些潜在的缺陷和限制:

  1. 循环引用:该函数没有处理循环引用的逻辑。如果对象中存在循环引用(即对象属性直接或间接地引用了自己),函数会陷入无限递归,导致栈溢出错误。
  2. 特殊类型:该函数没有处理如 Date、RegExp、Function、Error、Map、Set、BigInt、Symbol 等特殊类型的对象。这些类型的对象在直接复制时可能无法保持其原始状态或行为。
  3. 性能:对于非常大的对象或深度嵌套的对象,递归可能会导致性能问题。虽然现代JavaScript引擎对递归做了优化,但在某些情况下,使用循环而非递归可能会更有效。
  4. 不可枚举属性:for...in 循环只遍历对象自身的可枚举属性。如果对象有不可枚举的属性(例如通过 Object.defineProperty 定义的属性),这些属性将不会被复制到新对象中。
  5. getter/setter:如果对象的属性是通过 getter/setter 方法定义的,那么简单地复制属性值可能不是您想要的行为。您可能希望在新对象上也保持这些 getter/setter 方法。
  6. Buffer 和其他类型化数组:如果对象包含 Node.js 中的 Buffer 或其他类型化数组(如 Uint8Array),则简单的复制可能不会按预期工作。
  7. 原型链:该函数只复制了对象自身的属性,而没有复制原型链上的属性。在某些情况下,您可能希望保持原型链的完整性。
  8. 未考虑 null undefined 作为对象属性的值:虽然函数处理了 null 和 undefined 作为整体输入的情况,但如果它们作为对象的属性值出现(例如 { prop: null } 或 { prop: undefined }),则会被正常复制。但在某些情况下,您可能希望对这些值进行特殊处理。

为了解决这些问题,您可能需要扩展 deepClone 函数以包含额外的逻辑来处理上述特殊情况。另外,也有一些现成的库(如 lodash 的 _.cloneDeep 方法)提供了更强大和灵活的深拷贝功能。

手写浅拷贝

手写浅拷贝(Shallow Copy)通常指的是复制对象的顶层属性,而不是递归地复制对象的所有子属性。在 JavaScript 中,浅拷贝可以通过多种方式实现,包括使用扩展运算符(...)、Object.assign() 方法,或者通过循环遍历对象的属性。
以下是几种实现浅拷贝的方法:

1. 使用扩展运算符(Spread Operator)

function shallowCopy1(obj) {  
  return {...obj};  
}

2. 使用 Object.assign() 方法

function shallowCopy2(obj) {  
  return Object.assign({}, obj);  
}

3. 使用循环遍历属性

function shallowCopy3(obj) {  
  if (typeof obj !== 'object' || obj === null) {  
    return obj;  
  }  
    
  let copy = Array.isArray(obj) ? [] : {};  
    
  for (let key in obj) {  
    if (obj.hasOwnProperty(key)) {  
      copy[key] = obj[key];  
    }  
  }  
    
  return copy;  
}

请注意,这些方法在处理数组、对象以及基本类型时表现良好,但它们都是浅拷贝,这意味着如果对象的属性值是另一个对象或数组,那么新对象和原对象将引用相同的子对象或数组。

示例

const original = {  
  a: 1,  
  b: { c: 2 },  
  d: [3, 4]  
};  
  
const copy1 = shallowCopy1(original);  
const copy2 = shallowCopy2(original);  
const copy3 = shallowCopy3(original);  
  
// 修改原始对象的子对象或数组  
original.b.c = 3;  
original.d.push(5);  
  
// 浅拷贝的对象也会受到影响,因为它们引用了相同的子对象或数组  
console.log(copy1.b.c); // 输出 3  
console.log(copy2.d); // 输出 [3, 4, 5]  
console.log(copy3.b.c); // 输出 3

在这个示例中,由于浅拷贝,copy1、copy2 和 copy3 的 b 属性和 d 属性分别引用了与 original 相同的对象和数组。因此,当修改 original 的这些属性时,浅拷贝的对象也会受到影响。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker 容器网络及其配置说明

Docker 容器网络及其配置说明 docker容器网络docker的4种网络模式bridge 模式container模式host 模式none 模式应用场景 docker 容器网络配置Linux 内核实现名称空间的创建创建 Network Namespace操作 Network Namespace 转移设备veth pair创建 veth pair实现 Network Namespac…

缓存-分布式锁-原理和基本使用

分布式锁原理和使用 自旋 public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {Boolean b redisTemplate.opsForValue().setIfAbsent(Lock, Lock, Duration.ofMinutes(1));if (!b) {int i 10;while (i > 0) {Object result redisTe…

【QT】容器类控件

目录 概述 Group Box 核心属性 Tab Widget 核心属性 核心信号 核心方法 使用示例&#xff1a; 布局管理器 垂直布局 核心属性 使用示例&#xff1a; 水平布局 核⼼属性 (和 QVBoxLayout 属性是⼀致的) 网格布局 核心属性 使用示例&#xff1a; 示例&#x…

【python】python猫眼电影数据抓取分析可视化(源码+数据集+论文)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

安卓虚拟位置修改

随着安卓系统的不断更新&#xff0c;确保软件和应用与最新系统版本的兼容性变得日益重要。本文档旨在指导用户如何在安卓14/15系统上使用特定的功能。 2. 系统兼容性更新 2.1 支持安卓14/15&#xff1a;更新了对安卓14/15版本的支持&#xff0c;确保了软件的兼容性。 2.2 路…

Xilinx FPGA:vivado串口输入输出控制fifo中的数据

一、实验要求 实现同步FIFO回环测试&#xff0c;通过串口产生数据&#xff0c;写入到FIFO内部&#xff0c;当检测到按键信号到来&#xff0c;将FIFO里面的数据依次读出。 二、信号流向图 三、状态转换图 四、程序设计 &#xff08;1&#xff09;按键消抖模块 timescale 1ns…

批量文本编辑管理神器:一键修改多处内容,轻松转换编码,助力工作效率飞跃提升!

在信息爆炸的时代&#xff0c;文本处理已成为我们日常工作中不可或缺的一部分。无论是处理文档、整理数据还是编辑资料&#xff0c;都需要对大量的文本进行管理和修改。然而&#xff0c;传统的文本编辑方式往往效率低下&#xff0c;容易出错&#xff0c;难以满足现代工作的高效…

QListWidget 缩略图IconMode示例

1、实现的效果如下&#xff1a; 2、实现代码 &#xff08;1&#xff09;头文件 #pragma once #include <QtWidgets/QMainWindow> #include "ui_QListViewDemo.h" enum ListDataType { ldtNone -1, ldtOne 0, ldtTwo 1, }; struct ListData…

树莓派4B_OpenCv学习笔记19:OpenCV舵机云台物体追踪

今日继续学习树莓派4B 4G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 版本是4.5.1&#xff1a; Python 版本3.7.3&#xff1a; ​​ 今日学习&#xff1…

Apache Seata应用侧启动过程剖析——RM TM如何与TC建立连接

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 Apache Seata应用侧启动过程剖析——RM & TM如何与TC建立连接 前言 看过官网 README 的第…

Python | Leetcode Python题解之第217题存在重复元素

题目&#xff1a; 题解&#xff1a; class Solution(object):def containsDuplicate(self, nums):if len(set(nums)) ! len(nums):return Trueelse:return False

TCP一定可靠吗

背景 公司某个服务发送TCP报文后,得到的响应是非预期数据 原因竟然是:TCP包的 payload 数据某个bit位被翻转,但是 checksum 的值一样,错误的包被分发给了上层服务 Checksum介绍 IP 头有自己的 Checksum,TCP、UDP 也有自己的 Checksum,分别校验不同部分的数据 IP 头的 …

赛元单片机开发工具SOC_Programming_Tool_Enhance_V1.50 分享

下载地址&#xff1a; SOC_Programming_Tool_Enhance_V1.50(LIB0D30).rar: https://545c.com/f/45573183-1320016694-557ebd?p7526 (访问密码: 7526)

使用Spring Boot和自定义缓存注解优化应用性能

在现代应用开发中&#xff0c;缓存是提高系统性能和响应速度的关键技术之一。Spring Boot提供了强大的缓存支持&#xff0c;但有时我们需要更灵活的缓存控制。本文将介绍如何使用Spring Boot和自定义缓存注解来优化应用性能。 1. 为什么需要自定义缓存注解&#xff1f; Sprin…

干货 | 2024大模型场景下智算平台的设计与优化实践(免费下载)

诚挚邀请您微信扫描以下二维码加入方案驿站知识星球&#xff0c;获取上万份PPT/WORD解决方案&#xff01;&#xff01;&#xff01;感谢支持&#xff01;&#xff01;&#xff01;

在linux系统centos上面安装php7gmp扩展

ps:在ubuntu上面安装gmp(最简单) $ sudo apt-get install php7.0-gmp然后再php.ini添加extensionphp_gmp.so <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<…

Vue3中生成本地pdf并下载

1. 前言 前端中经常会遇到在系统中根据数据导出一个pdf文件出来,一般都是后端来实现的,既然后端可以实现,前端为什么就不行呢,正好有一次也写了这个需求,就写了个小demo 示例图: 2. 实现步骤 首先下载html2pdf.js这个库yarn add html2pdf.js // 或 npm i html2pdf.js在项…

欧洲杯数据控@20240706

点击标题下「蓝色微信名」可快速关注 上半区西班牙、法国脱颖而出&#xff0c;将会争夺一个决赛的席位&#xff0c;下半区两场比赛&#xff0c;将会决出另外两支进入半决赛的球队&#xff0c; 今日射手榜&#xff0c;随着球队的淘汰&#xff0c;能争夺金靴的球员越来越少了&…

17.优化算法之解决拓扑排序4

0.基础 1.课程表1 207. 课程表 - 力扣&#xff08;LeetCode&#xff09; class Solution {public boolean canFinish(int n, int[][] p) {// 1. 准备⼯作int[] in new int[n]; // 统计每⼀个顶点的⼊度Map<Integer, List<Integer>> edges new HashMap<>…

整洁架构SOLID-开闭原则(OCP)

文章目录 1 定义2 最佳实践2.1 需求2.2 需求变更2.3 变更原则2.4 实现逻辑2.4.1 组件化2.4.2 组件关系 2.5 依赖方向的控制 3 本章小结 1 定义 开闭原则(OCP)是Bertrand Meyer在1988年提出的&#xff0c;该设计原则认为&#xff1a; 设计良好的计算机软件应该易于扩展&#xf…