javascript基础五:深拷贝浅拷贝的区别?如何实现一个深拷贝?

在这里插入图片描述
一、数据类型存储
JavaScript中存在两大数据类型:

  • 基本类型
  • 引用类型

基本类型数据保存在在栈内存中
引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中

二、浅拷贝
浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址
即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址
下面简单实现一个浅拷贝

function shallowClone(obj){
    const newObj = {}
    for(let prop in obj){
        if(obj.hasOwnProperty(prop)){
            newObj[prop]=obj[prop]
        }
    }
    return newObj
}

在这里插入图片描述
在JavaScript中,存在浅拷贝的现象有:

  • Object.assign
  • Array.prototype.slice(), Array.prototype.concat()
  • 使用拓展运算符实现的复制

Object.assign

var obj = {
    age:20,
    list:['1','2'],
    name:{
        name:'小爱同学'
    },
    fn:function(){
        console.log('this is fn')
    }
}
undefined
var obj1 = Object.assign({},obj)
undefined
obj
{age: 20, list: Array(2), name: {…}, fn: ƒ}age: 20fn: ƒ ()list: (2) ['1', '2']name: {name: '小爱同学'}[[Prototype]]: Object
obj.age = 21
21
obj1 = Object.assign({},obj)
{age: 21, list: Array(2), name: {…}, fn: ƒ}
obj1
{age: 21, list: Array(2), name: {…}, fn: ƒ}age: 21fn: ƒ ()list: (2) ['1', '2']name: {name: '小爱同学'}[[Prototype]]: Object
obj.age = 22
22
obj1
{age: 21, list: Array(2), name: {…}, fn: ƒ}
obj.list.push('3')
3
obj1
{age: 21, list: Array(3), name: {…}, fn: ƒ}age: 21fn: ƒ ()list: (3) ['1', '2', '3']name: {name: '小爱同学'}[[Prototype]]: Object

在这里插入图片描述
slice()

const arr = [1,2,3,4,5]
undefined
const arr1 = arr.slice()
undefined
const arr1 = arr.slice(0)
undefined
arr1
(5) [1, 2, 3, 4, 5]
arr1[0] = -1
-1
arr
(5) [1, 2, 3, 4, 5]
arr1
(5) [-1, 2, 3, 4, 5]

在这里插入图片描述
concat()

let arr = [1,2,3,4,5]
undefined
let arr1 = arr.concat()
undefined
arr1[0] = -1
-1
arr
(5) [1, 2, 3, 4, 5]
arr1
(5) [-1, 2, 3, 4, 5]

在这里插入图片描述
拓展运算符

let arr = [1,2,3,4,5]
undefined
let arr1 = [...arr]
undefined
arr1[0] = -1
-1
arr
(5) [1, 2, 3, 4, 5]
arr1
(5) [-1, 2, 3, 4, 5]

在这里插入图片描述
三、深拷贝
深拷贝开辟一个新的栈,两个对象属性完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式有:

  • _.cloneDeep()
  • jQuery.extend()
  • JSON.stringify()
  • 手写循环递归

_.cloneDeep()

const _ = require('lodash');
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false

jQuery.extend()

const $ = require('jquery');
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false

JSON.stringify()

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

undefined
let obj = JSON.parse(JSON.stringify(obj1))
undefined
obj
{a: 1, b: 2, c: 3}

在这里插入图片描述
但是这种方式存在弊端,会忽略undefined、symbol和函数

const obj = {
    name: 'A',
    name1: undefined,
    name3: function() {},
    name4:  Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}

在这里插入图片描述
循环递归


function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}

四、区别
下面首先借助两张图,可以更加清晰看到浅拷贝与深拷贝的区别
在这里插入图片描述
在这里插入图片描述

从上图发现,浅拷贝和深拷贝都创建出一个新的对象,但在复制对象属性的时候,行为就不一样
浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象

// 浅拷贝
const obj1 = {
    name : 'init',
    arr : [1,[2,3],4],
};
const obj3=shallowClone(obj1) // 一个浅拷贝方法
obj3.name = "update";
obj3.arr[1] = [5,6,7] ; // 新旧对象还是共享同一块内存

console.log('obj1',obj1) // obj1 { name: 'init',  arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj3',obj3) // obj3 { name: 'update', arr: [ 1, [ 5, 6, 7 ], 4 ] }

但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象

// 深拷贝
const obj1 = {
    name : 'init',
    arr : [1,[2,3],4],
};
const obj4=deepClone(obj1) // 一个深拷贝方法
obj4.name = "update";
obj4.arr[1] = [5,6,7] ; // 新对象跟原对象不共享内存

console.log('obj1',obj1) // obj1 { name: 'init', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4',obj4) // obj4 { name: 'update', arr: [ 1, [ 5, 6, 7 ], 4 ] }

小结
前提为拷贝类型为引用类型的情况下:

  • 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
  • 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址

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

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

相关文章

( 数组) 209. 长度最小的子数组——【Leetcode每日一题】

❓209. 长度最小的子数组 难度:中等 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,…

在线Excel绝配:SpreadJS 16.1.1+GcExcel 6.1.1 Crack

前端:SpreadJS 16.1.1 后端: GcExcel 6.1.1 全能 SpreadJS 16.1.1此版本的产品中包含以下功能和增强功能。 添加了各种输入掩码样式选项。 添加了在保护工作表时设置密码以及在取消保护时验证密码的支持。 增强了组合图以将其显示为仪表图。 添加了…

Tugraph的设计和源码初步解析

1. Tugraph Tugraph是一款开源的性能优秀的图数据库,该图数据库使用多版本的BTree作为数据的存储引擎,同时设置了点边数据在这个存储引擎上的布局(主要考虑数据的局部性),从而达到高性能查询的目的。本文主要从Tugrap…

研发工程师玩转Kubernetes——自动扩缩容

在《研发工程师玩转Kubernetes——使用Deployment进行多副本维护》一文中,我们通过Deployment实现了多副本维护——即维持在一个确定数量的副本个数。而在现实场景中,我们往往需要根据服务的压力,采用水平(横向)扩容的…

Springboot +spring security,前后端分离时的security处理方案(二)

一.简介 在前后端分离这样的开发模式下,前后端的交互都是通过 JSON 来进行数据传递的,无论登录成功还是失败,都不会有服务端跳转或者客户端跳转之类的操作。 也就是说无论登录成功还是失败,服务端都会返回一段登录成功或失败的 …

使用【Python+Appium】实现自动化测试

一、环境准备 1.脚本语言:Python3.x IDE:安装Pycharm 2.安装Java JDK 、Android SDK 3.adb环境,path添加E:\Software\Android_SDK\platform-tools 4.安装Appium for windows,官网地址 Redirecting 点击下载按钮会到GitHub的…

解决dpdk reserve的内存返回的虚拟地址和iova地址一样的问题

1. 背景: 在ubuntu20.04上用dpdk API: rte_memzone_reserve_aligned("L1L2_PCIE_MEMORY", 1.5*1024*1024*1024, rte_socket_id(), RTE_MEMZONE_1GB|RTE_MEMZONE_IOVA_CONTIG, RTE_CACHE_LINE_SIZE); 分配1.5…

如何在 GNU Linux 上通过 Nvm 安装 Node 和 Npm?

Node.js 是一个流行的 JavaScript 运行时环境,用于开发服务器端和网络应用程序。它带有一个强大的软件包管理器 npm,可以方便地安装和管理 JavaScript 包和依赖项。在 GNU/Linux 系统上,使用 Nvm(Node Version Manager&#xff09…

Framework开发环境搭建

Framework开发环境搭建 开启Android Framework之旅,一步步记录自己学习过程。 硬件配置 RAM:最低16GB,建议32GB,有条件64GB,内存越高,编译时间越短ROM:最低400GB,代码250GB构建15…

Svn安装

目录 一. 软件环境 二. SVN服务端 1. yum安装svn 2. 查看安装的文件列表 3. 建立版本库 3.1 修改数据存储默认位置 3.2 使用svnadmin建立版本库 4. 配制 4.1 添加用户 4.2 配制读写权限 4.3 配制服务 5. 启动服务 5.1 停止服务 5.2 启动服务 5.3 拉取项目 三.…

Zookeeper集群 + Kafka集群

Zookeeper 概述 Zookeeper 定义 Zookeeper是一个开源的分布式的,为分布式框架提供协调服务的Apache项目。 Zookeeper 工作机制 Zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的…

CodeForces.1786A2.发牌.[中等][flg标识][数学规律][双色牌]

题目描述: 题目解读: 发牌问题,给两人发双色牌,同样还是 给a发1张,然后给b发2,3张; 给a发4,5张,给b发6,7张; 给a发8,9张&#xff…

《最新出炉》Python+Playwright自动化测试-2-playwright的API及其他知识

一.简介 上一篇我已经将PythonPlaywright的环境搭建好了,而且也简单的演示了一下三款浏览器的启动和关闭,是不是很简单啊。今天主要是把一篇的中的代码进行一次详细的注释,然后说一下playwright的API和其他相关知识点。那么首先将上一篇中的…

Spring Cloud面试题

组件 Spring Cloud Eureka:服务注册与发现 Spring Cloud Zuul:服务网关 Spring Cloud Ribbon:客户端负载均衡 Spring Cloud Feign:声明性的Web服务客户端 Spring Cloud Hystrix:断路器 Spring Cloud Config&#xff1…

[C++]octomap安装后测试

测试环境&#xff1a; vs2019 octomap1.9.6 release x64 代码&#xff1a; #include <octomap/octomap.h> #include <octomap/OcTree.h> using namespace std; using namespace octomap; void print_query_info(point3d query, OcTreeNode* node) { if (…

矿井水除氟,污水除氟的工艺分析

高矿化度的废水是指含有高浓度溶解性矿物质的废水&#xff0c;通常指的是含有高浓度钠、钙、镁、铁、铝、钾等离子的废水。这些离子通常来自于废水所处的环境、工业或生产过程中使用的原材料和化学品。高矿化度的废水通常具有高盐度、高电导率、高硬度等特征&#xff0c;对环境…

Vivado综合属性系列之十二 BLACK_BOX

目录 一、前言 二、BLACK_BOX ​2.1 属性说明 ​2.2 工程代码 ​2.3 结果 一、前言 ​在调试中&#xff0c;有时不需要知道一个模块或实例的具体实现&#xff0c;或者需要使其对外属于不可见&#xff0c;只知道它的输入输出&#xff0c;即像一个黑盒&#xff0c;此时可以对模…

港联证券|“面值退”增多凸显A股市场化进程良性态势

近日&#xff0c;多家陷入“1元退市”危机的公司纷纷发布风险提示公告称&#xff0c;公司股票存在可能因股价低于面值被终止上市的风险。据《经济参考报》记者不完全统计&#xff0c;今年以来&#xff0c;沪深两市已有10余只个股锁定“面值退”&#xff0c;其中多以披星戴帽公司…

Windows平台下用例图中包含(include)、扩展(extend)和泛化(generalization)介绍

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天总结一下Windows平台下用例图中包含(include)、扩展(extend)和泛化(generalization&#xff09;介绍。 用例图是解决用户需求的图&#xff0c;画好用例图一定要理清用例之间的关系。用例之间有三种关系&…

什么是跳表

什么是跳表 跳表全称为跳跃列表&#xff0c;它允许快速查询&#xff0c;插入和删除一个有序连续元素的数据链表。跳跃列表的平均查找和插入时间复杂度都是O(logn)。快速查询是通过维护一个多层次的链表&#xff0c;且每一层链表中的元素是前一层链表元素的子集&#xff08;见右…