彻底理解浅拷贝和深拷贝

news2024/9/21 2:42:34

目录

  • 浅拷贝
    • 实现
  • 深拷贝
    • 实现
    • 自己手写

浅拷贝

浅拷贝是指创建一个新对象,这个对象具有原对象属性的精确副本

  • 基本数据类型(如字符串、数字等),在浅拷贝过程中它们是通过值传递的,而不是引用传递,修改值并不会影响原对象

  • 如果这些属性是引用类型(如对象、数组等),浅拷贝只会复制它们的引用,而不会复制它们的内容

  • 浅拷贝后的新对象和原对象中的引用类型属性仍然指向相同的内存地址,修改其中一个的引用类型数据,会影响另一个

实现

  • Object.assign(target, source):用于将所有可枚举的属性从一个或多个源对象复制到目标对象,返回目标对象

    const obj = {
      name: "obj",
      age: 18,
      height: 180,
      o: {
        a: 1,
        b: 2,
      },
    };
    const copy = Object.assign({ height: 188 }, obj);
    console.log(copy); // {height: 180, name: 'obj', age: 18, o:{...}}
    copy.name = "copy";
    console.log(obj.name); // obj
    console.log(copy.name); // copy
    copy.o.a = 11;
    console.log(copy.o); // {a: 11, b: 2}
    console.log(obj.o); // {a: 11, b: 2}
    
  • 数组的 slice() 方法:对于数组,slice() 方法可以用来进行浅拷贝。它返回一个新数组,并将原数组中的元素逐个复制到新数组中,但如果数组的元素是对象,它们仍然共享相同的引用

    const names = ["abc", "def", { name1: "ghi", name2: "cba" }];
    const copy = names.slice(1);
    console.log(copy); // ['def', {name1: 'ghi', name2: 'cba'}]
    copy[1].name1 = "abc";
    console.log(copy[1]); // {name1: 'abc', name2: 'cba'}
    console.log(names[2]); // {name1: 'abc', name2: 'cba'}
    
  • 扩展运算符(spread operator ...):适用于数组和对象

    const obj = {
      name: "obj",
      age: 18,
      height: 180,
      o: {
        a: 1,
        b: 2,
      },
    };
    const names = ["abc", "def", { name1: "ghi", name2: "cba" }];
    const copy1 = { ...obj };
    const copy2 = [ ...names ];
    copy1.o.a = 11;
    console.log(copy1.o); // {a: 11, b: 2}
    console.log(obj.o); // {a: 11, b: 2}
    
    copy2[1].name1 = "abc";
    console.log(copy2[2]); // {name1: 'abc', name2: 'cba'}
    console.log(names[2]); // {name1: 'abc', name2: 'cba'}
    

实现的图解:

在这里插入图片描述

浅拷贝对基本数据类型有效,但对于对象、数组等引用类型,只是复制了它们的引用,这会导致在修改拷贝时,原对象也被修改。如果需要对嵌套对象和数组进行完全独立的拷贝,则需要使用深拷贝

深拷贝

深拷贝是指将一个对象的所有属性都完整地复制到另一个对象中,包括嵌套的对象或数组。深拷贝与浅拷贝不同,浅拷贝只复制对象的引用,而深拷贝会递归地复制对象的所有层次,确保原始对象和新对象完全独立,任何一方的修改不会影响另一方

实现

  • JSON实现:这是最简单的一种方式,适合处理不包含函数、undefinedSymbol、循环引用等特殊类型的对象,序列化有问题的情况如下:

    • undefined 不会被序列化,且在对象属性值中会被删除,在数组中则会被转化为 null

    • Symbol 是唯一的标识符,无法被序列化,且会被丢弃

    • Date 对象会被序列化为字符串,但当反序列化时,它不再是 Date 对象,而是一个普通字符串

    • 如果对象有循环引用,JSON.stringify 会抛出错误,因为它无法处理递归结构

    • MapSet 结构会被序列化为空对象,并且在反序列化时,无法恢复为原始结构

    • 不会序列化对象的原型链属性,因此对象的继承关系会丢失

    const set = new Set();
    const obj = {
      name: "obj",
      age: 18,
      height: undefined,
      o: {
        a: 1,
        b: 2,
      },
      [Symbol()]: "symbol",
      [set]: set,
      date: new Date()
    };
    console.log(JSON.parse(JSON.stringify(obj))); // {name: 'obj', age: 18, o: {…}, [object Set]: {}, date: "2024-09-14T06:48:44.497Z"}
    
  • 使用 structuredClone():在一些现代浏览器中,可以使用内置的 structuredClone() 来实现深拷贝。它可以处理大多数情况下的深拷贝需求,包括循环引用、DateMapSet

    • 不能拷贝 Symbol属性Symbol 类型属性会被忽略,因为 Symbol 是唯一的标识符,具有不可枚举性和唯一性

    • 拷贝 Symbol 值会报错Failed to execute 'structuredClone' on 'Window': Symbol() could not be cloned

    • 不会拷贝对象的原型链属性

    const obj = {
      name: "Alice",
      age: undefined,
      [Symbol()]: "symbol", // 会忽略
    };
    
    const clone = structuredClone(obj);
    console.log(clone); // { name: 'Alice', age: undefined }
    
  • 使用 Lodash 库中的 _.cloneDeep():Lodash 是一个非常流行的 JavaScript 工具库,其中提供了 _.cloneDeep() 方法,可以轻松实现深拷贝,对于大部分普通对象、数组、SetMap能够正确处理并进行深拷贝

    • 无法深拷贝 Symbol 属性,但可以克隆对象中以 Symbol 作为值的属性
    const sym = Symbol('id');
    const obj = {
      [sym]: 'value',
      id: Symbol('id'),
      name: 'Alice'
    };
    const clone = _.cloneDeep(obj);
    console.log(clone); // { id: Symbol(id), name: 'Alice' } -- Symbol 属性被忽略,Symbol 值被正确克隆
    

自己手写

  • 实现对对象和基本数据类型的拷贝
  • Symbolkey 进行处理
  • 其他数据类型的值进程处理:数组、函数、SymbolSetMap
  • 对循环引用的处理
function deepCopy(originValue, map = new WeakMap()) {
  // 0.如果值是Symbol的类型
  if (typeof originValue === "symbol") {
    return Symbol(originValue.description)
  }
  // 1.如果是原始类型, 直接返回
  if (!isObject(originValue)) {
    return originValue
  }
  // 2.如果是set类型
  if (originValue instanceof Set) {
    const newSet = new Set()
    for (const setItem of originValue) {
      newSet.add(deepCopy(setItem))
    }
    return newSet
  }
  // 3.如果是函数function类型, 不需要进行深拷贝
  if (typeof originValue === "function") {
    return originValue
  }
  // 4.如果是对象类型, 才需要创建对象
  if (map.get(originValue)) {
    return map.get(originValue)
  }
  const newObj = Array.isArray(originValue) ? []: {}
  map.set(originValue, newObj)
  // 遍历普通的key
  for (const key in originValue) {
    newObj[key] = deepCopy(originValue[key], map);
  }
  // 单独遍历symbol
  const symbolKeys = Object.getOwnPropertySymbols(originValue)
  for (const symbolKey of symbolKeys) {
    newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey], map)
  }
  return newObj
}

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

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

相关文章

基于yolov8的茶叶病害检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的茶叶病害检测系统,是利用深度学习技术,特别是YOLOv8这一先进的目标检测算法,来精准识别和监测茶叶生长过程中出现的各种病害。该系统通过无人机、地面机器人或固定摄像头等设备,定期采集茶园的高分辨率…

力扣刷题(6)

两数之和 II - 输入有序数组 两数之和 II - 输入有序数组-力扣 思路: 因为该数组是非递减顺序排列,因此可以设两个左右下标当左右下标的数相加大于target时,则表示右下标的数字过大,因此将右下标 - -当左右下标的数相加小于targ…

??Ansible——ad-hoc

文章目录 一、ad-hoc介绍二、ad-hoc的使用1、语法2、ad-hoc常用模块1)shell模块2)command模块3)script模块4)file模块5)copy模块6)yum模块7)yum-repository模块8)service模块9&#…

优化算法(一)—遗传算法(Genetic Algorithm)附MATLAB程序

遗传算法(Genetic Algorithm, GA)是一种启发式搜索算法,用于寻找复杂优化问题的近似解。它模拟了自然选择和遗传学中的进化过程,主要用于解决那些传统算法难以处理的问题。 遗传算法的基本步骤: 初始化种群&#xff0…

【GO语言】Go语言详解与应用场景分析,与Java的对比及优缺点

Go is an open source programming language that makes it easy to build simple, reliable, and efficient software. Go是一种开源编程语言,可以轻松构建简单、可靠和高效的软件。 文章目录 一、引言二、Go语言详解1. 简史2. 特点3. 核心库 三、应用场景四、与Ja…

comfyui中,sam detector与yoloworld图像分割算法测试以及影响

🍖背景 图像处理中,经常会用到图像分割,在默认的comfyui图像加载中就有一个sam detector的功能,yoloworld是前一段时间公开的一个更强大的图像分割算法,那么这两个差别大吗?在实际应用中有什么区别吗&…

普推知产:明知商标驳回也要去申请注册!

有个去年加的网友让普推知产商标老杨看在32类申请如何,去年是把33类的申请复审下来,这个网友想的名称都是存在已存在的商标名称,直接都是申请不下来的,需要申请和再加驳回复审。 去年那个在33类的名称,当时查过只有一个…

函数(下)

static 代码1的test函数中的局部变量i是每次进⼊test函数先创建变量(⽣命周期开始)并赋值为0,然后 ,再打印,出函数的时候变量⽣命周期将要结束(释放内存)。 代码2中,我们从输出结果…

论文阅读-Demystifying Misconceptions in Social Bots Research

论文链接: https://arxiv.org/pdf/2303.17251 目录 摘要: Introduction Methodological issues Information leakage Cherry-picking(采摘樱桃) Straw-man methodology (稻草人) Data biases Conceptual issu…

Spring高手之路23——AOP触发机制与代理逻辑的执行

文章目录 1. 从整体视角学习Bean是如何被AOP代理的2. AOP代理的触发机制2.1 postProcessAfterInitialization方法源码分析2.2 wrapIfNecessary方法源码分析2.3 时序图演示触发机制 3. AOP代理逻辑的执行3.1 AOP代理如何使用拦截器3.2 proceed方法源码分析3.3 时序图 1. 从整体视…

【Linux】线程锁条件变量信号量生产消费者模型线程池

文章目录 线程概念线程控制接口和线程id线程优缺点线程互斥和条件变量锁和条件变量相关接口POSIX 信号量生产消费者模型阻塞队列实现生产消费者模型环形队列实现生产消费者模型简易懒汉线程池自旋锁和读写锁(了解) 线程概念 在操作系统的的视角下&#x…

SysML图例-农业无人机

DDD领域驱动设计批评文集>> 《软件方法》强化自测题集>> 《软件方法》各章合集>>

828华为云征文 | 华为云FlexusX实例下的Kafka集群部署实践与性能优化

前言 华为云FlexusX实例,以创新的柔性算力技术,为Kafka集群部署带来前所未有的性能飞跃。其灵活的CPU与内存配比,结合智能调度与加速技术,让Kafka在高并发场景下依然游刃有余。在828华为云企业上云节期间,FlexusX实例携…

亲测好用,ChatGPT 3.5/4.0新手使用手册,最好论文指令手册~

本以为遥遥领先的GPT早就普及了,但小伙伴寻找使用的热度一直高居不下,其实现在很简单了! 国产大模型快200家了,还有很多成熟的国内AI产品,跟官网一样使用,还更加好用~ ① 3.5 大多数场景是够用的&#xff…

【Java】多线程:Thread类并行宇宙

欢迎浏览高耳机的博客 希望我们彼此都有更好的收获 感谢三连支持! 在现代编程中,多线程是提高程序性能和响应能力的一种重要手段。Java 通过 Thread 类和 Runnable 接口提供了丰富的线程管理功能。本文是对 Thread 类基本用法的总结。 线程创建 线程可以…

Ubuntu 22.04上安装Java JDK 8

在Ubuntu 22.04上安装Java JDK 8可以通过以下步骤完成: 前言 本文特别感谢浪浪云的赞助发布。浪浪云,其卓越的云服务和技术支持,一直致力于为用户提供高效、可靠的解决方案。无论是个人开发者、小型企业还是大型组织,浪浪云都能…

11.01类的定义和对象的使用(练习)

类的定义 类名:手机(Phone) 成员变量:品牌(brand),价格(price) 成员方法:打电话(calL),发短信(sendMessage) 调用类变量和方法

商标申请注册加字加成通用词等于没加!

以前普推知产商标曾分析过“东方甄选”火遍全网后,许多人申请注册商标都喜欢加“甄选”,但是“甄选”基本属于通用词了,加“甄选”后还是属于前面那个词。 近期看到有人加“心选”,甄选,优选,心选等还都是选…

HTTPTomcat

HTTP&Tomcat&Servlet 今日目标: 了解JavaWeb开发的技术栈理解HTTP协议和HTTP请求与响应数据的格式掌握Tomcat的使用掌握在IDEA中使用Tomcat插件 1,Web概述 1.1 Web和JavaWeb的概念 Web是全球广域网,也称为万维网(www),…

day01 - Java基础语法

第一章 Java概述 1995年美国Sun推出Java,2009年Sun公司被甲骨文收购 Java之父:詹姆斯高斯林(James Gosling) Java编译器将源程序编译成与平台无关的字节码文件(class文件),然后由JVM对字节码文件解释执行。不同操作系统&#xf…