当前位置: 首页 > news >正文

详解js中的浅拷贝与深拷贝

详解js中的浅拷贝与深拷贝

    • 1、前言
      • 1.1 栈(stack)和堆(heap)
      • 1.2 基本数据类型和引用数据类型
        • 1.2.1 概念
        • 1.2.2 区别
        • 1.2.3 基本类型赋值方式
        • 1.2.4 引用类型赋值方式
    • 2、浅拷贝
      • 2.1 概念
      • 2.2 常见的浅拷贝方法
        • 2.2.1 Object.assign()
        • 2.2.2 扩展运算符(...)
        • 2.2.3 Array.concat()
        • 2.2.4 Array.slice()
    • 3、深拷贝
      • 3.1 概念
      • 3.2 常见的深拷贝方法
        • 3.2.1 JSON.parse(JSON.stringify(obj))
        • 3.2.2 递归
        • 3.2.2 函数库lodash
    • 4、总结
    • 5、应用场景

1、前言

1.1 栈(stack)和堆(heap)

  • 栈(stack):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;
  • 堆(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表

1.2 基本数据类型和引用数据类型

1.2.1 概念

  • 基本数据类型:Number、String、Boolean、Null、 Undefined、Symbol(ES6);
  • 引用数据类型:Object、Array、Function、Date、RegExp、Map、Set等。

1.2.2 区别

两者区别:

  1. 内存地址分配:
    1)基本数据类型:将值存储在栈中 ,栈中存放的是对应的值
    2)引用数据类型:将对应的值存储在堆中,栈中存放的是指向堆内存的地址
  2. 赋值变量:
    1)基本数据类型:是生成相同的值,两个对象对应不同的地址
    2)引用数据类型:是将保存对象的内存地址赋值给另一个变量。也就是两个变量指向堆内存中同一个对象
	let a = 10;let b = a;  // 赋值操作b = 100;console.log(a);  // 10

总结:
a是基本类型,存储在栈中;把a赋值给b,虽然两个变量的值相等,但是两个变量保存了两个不同的内存地址。

1.2.3 基本类型赋值方式

在这里插入图片描述

1.2.4 引用类型赋值方式

	let obj1 = {}let obj2 = objobj2.name = '李四'console.log(obj.name)  // 李四

理解:obj1是引用类型,将数据存放在堆内存中,而栈中存放的是内存地址.在obj1赋值给obj2,实际是将obj1的引用地址复制了一份给了obj2,实际上他们共同指向了同一个堆内存对象,所以更改obj2会对obj1产生影响
在这里插入图片描述

2、浅拷贝

2.1 概念

会在栈中开辟另一块空间,并将被拷贝对象的栈内存数据完全拷贝到该块空间中,即基本数据类型的值会被完全拷贝,而引用类型的值则是拷贝了“指向堆内存的地址”。

2.2 常见的浅拷贝方法

  • Object.assign()
  • 扩展运算符(…)
  • Array.concat()
  • Array.slice()

2.2.1 Object.assign()

object.assign 是 ES6 中 object 的一个方法,该方法可以用于JS 对象的合并等多个用途,其中一个用途就是可以进行浅拷贝。

object.assign 的语法为:Object.assign(target, …sources)

var obj = { x: 1, y: 2,z: { num: 10 } 
}
var newObj = {}
Object.assign(newObj, obj)
newObj.y = 3
console.log(obj)  
console.log(newObj) 

运行结果如下:
在这里插入图片描述

注意:

  • Object.assign()不会拷贝对象的继承属性;
  • Object.assign()不会拷贝对象的不可枚举的属性;
  • Object.assign()可以拷贝 Symbol 类型的属性。

2.2.2 扩展运算符(…)

扩展运算符的语法为:let cloneObj = { …obj };

/* 对象的拷贝 */
const obj = {a: 1,b: {c: 1}
}
const obj2 = {...obj
}
obj.a = 2console.log(obj)
console.log(obj2); obj.b.c = 2console.log(obj) 
console.log(obj2); /* 数组的拷贝 */
let arr = [1, 2, 3];
let newArr = [...arr]; // 跟arr.slice()是一样的效果

运行结果如下:
在这里插入图片描述

注意:扩展运算符 和 object.assign 有同样的缺陷,也就是实现的浅拷贝的功能差不多,但是如果属性都是基本类型的值,使用扩展运算符进行浅拷贝会更加方便。

2.2.3 Array.concat()

var obj1 = ["Chinese", { "name": "zs" }, "French"]
var obj2 = obj1.concat()
obj2[1].name = "ls"
obj2[2] = "China"
console.log(obj1, obj2);

运行结果如下:
在这里插入图片描述
注意:数组的 concat 方法其实也是浅拷贝,所以连接一个含有引用类型的数组时,需要注意修改原数组中的元素的属性,因为它会影响拷贝之后连接的数组。不过 concat 只能用于数组的浅拷贝,使用场景比较局限。

2.2.4 Array.slice()

slice 的语法为:arr.slice(begin, end);

var arr = [2, 4, 6, { y: 10 }]
var newArr = arr.slice()
newArr[0] = 10
newArr[3].x = 20
newArr[3].y = 30
console.log(arr)
console.log(newArr)

运行结果如下:
在这里插入图片描述
注意:slice 方法也比较有局限性,因为它仅仅针对数组类型。slice 方法会返回一个新的数组对象,这一对象由该方法的前两个参数来决定原数组截取的开始和结束位置,是不会影响和改变原始数组的。但是,数组元素是引用类型的话,也会影响到原始数组。

3、深拷贝

3.1 概念

深拷贝是拷贝多层,每一级别的数据都会拷贝出来。

3.2 常见的深拷贝方法

  • JSON.parse(JSON.stringify(obj))
  • 递归方法
  • 函数库 lodash

3.2.1 JSON.parse(JSON.stringify(obj))

原理:用 JSON.stringify 将对象转成 JSON 字符串,再用 JSON.parse()
把字符串解析成对象。一去一来,新的对象就产生了,而且对象会开辟新的栈,实现深拷贝。

let a = {name: '张三', age: 19, like: ['打篮球', '唱歌', '跳舞']}
let b = JSON.parse(JSON.stringify(a))
a.name = '李四'
a.like[0] = '睡觉'
console.log(a)  
console.log(b)  

运行结果如下:
在这里插入图片描述
但是,JSON.stringify并不是那么完美的,它也有局限性。

  • 拷贝的对象的值中如果有函数、undefined、symbol 这几种类型,经过 JSON.stringify 序列化之后的字符串中这个键值对会消失;
  • 拷贝 Date 引用类型会变成字符串;
  • 无法拷贝不可枚举的属性;
  • 无法拷贝对象的原型链;
  • 拷贝 RegExp 引用类型会变成空对象;
  • 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null;
  • 无法拷贝对象的循环应用,即对象成环 (obj[key] = obj)。
let obj = {func: function () { alert(1) },obj: { a: 1 },arr: [1, 2, 3],und: undefined,reg: /123/,date: new Date(0),NaN: NaN,infinity: Infinity,sym: Symbol('1')
}Object.defineProperty(obj, 'innumerable', {enumerable: false,value: 'innumerable'
});console.log('obj', obj); // { NaN: NaN , arr: (3) [1, 2, 3] ,date: Thu Jan 01 1970 08:00:00 GMT+0800 (中国标准时间) {}, func: ƒ(), infinity: Infinity , obj: { a: 1 } , reg: /123/, sym: Symbol(1), und: undefined, innumerable: "innumerable" }const str = JSON.stringify(obj);const obj1 = JSON.parse(str);console.log('obj1', obj1); // { NaN: null, arr: (3) [1, 2, 3], date: "1970-01-01T00:00:00.000Z", infinity: null, obj: {a: 1}, reg: {} }

3.2.2 递归

 function deepCopyTwo(obj) {let objClone = Array.isArray(obj) ? [] : {};if (obj && typeof obj == 'object') {for (const key in obj) {//判断obj子元素是否为对象,如果是,递归复制if (obj[key] && typeof obj[key] === "object") {objClone[key] = deepCopyTwo(obj[key]);} else {//如果不是,简单复制objClone[key] = obj[key];}}}	return objClone;}

3.2.2 函数库lodash

lodash是一个著名的javascript原生库,不需要引入其他第三方依赖。是一个意在提高开发者效率,提高JS原生方法性能的JS库。简单的说就是,很多方法lodash已经帮你写好了,直接调用就行,不用自己费尽心思去写了,而且可以统一方法的一致性。Lodash使用了一个简单的_ 符号,就像Jquery的 $ 一样,十分简洁。lodash函数库提供 _.cloneDeep用来做深拷贝。

	let _ = require('lodash');let obj = {a:1,b:{f:{g:1}},c:[1,2,3]};let newObj = _cloneDeep(obj);console.log(obj.b.f === newObj.b.f); //true

4、总结

  • 浅拷贝就是只拷贝基础数据类型的数据,并且数据只有单一的一层,而深拷贝用于拷贝具有复杂的数据类型的数据

5、应用场景

  • 浅拷贝主要用于你需要拷贝的对象的数据结构只有基础数据类型,并且你不想改变原数据类型或者需要对比操作前后的数据。
  • 深拷贝主要用于你想操作该数据,但是又不想影响到原数据的时候,就可以进行深拷贝。

相关文章:

详解js中的浅拷贝与深拷贝

详解js中的浅拷贝与深拷贝 1、前言1.1 栈(stack)和堆(heap)1.2 基本数据类型和引用数据类型1.2.1 概念1.2.2 区别1.2.3 基本类型赋值方式1.2.4 引用类型赋值方式 2、浅拷贝2.1 概念2.2 常见的浅拷贝方法2.2.1 Object.assign()2.2.…...

Day9 敏捷测试——敏捷开发的特征、什么是敏捷测试?、极限编程、极限测试

Day9 敏捷测试——敏捷开发的特征、什么是敏捷测试?、极限编程、极限测试 文章目录 Day9 敏捷测试——敏捷开发的特征、什么是敏捷测试?、极限编程、极限测试敏捷开发的特征1、迭代式开发2、增量交付3、及时反馈4、持续集成5、自我管理敏捷开发和迭代式开发的根本区别1、性质…...

k8s 维护node与驱逐pod

1.维护node节点 设置节点状态为不可调度状态,执行以下命令后,节点状态会多出一个SchedulingDisabled的状态,即新建的pod不会往该节点上调度,本身存在node中的pod保持正常运行 kubectl cordon k8s-node01 kubectl get node 2.驱…...

SouapUI接口测试之创建性能测试

SouapUI也是一个能生动的体现一个系统(项目)性能状态的工具,本篇就来说说如何在SouapUI工具下创建性能测试 一、创建测试用例 由于在《SouapUI接口测试之使用Excel进行参数化》篇已经创建好了测试用例,本篇就不讲解如何创建测试…...

springboot整合kafka入门

kafka基本概念 producer: 生产者,负责发布消息到kafka cluster(kafka集群)中。生产者可以是web前端产生的page view,或者是服务器日志,系统CPU、memory等。 consumer: 消费者,每个consumer属于一个特定的c…...

Rust 笔记:Rust 语言中的字符串

Rust 笔记 Rust 语言中的字符串 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm1001.2101.3001.5343 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_28550263/article/detail…...

华为OD机试真题 Java 实现【将真分数分解为埃及分数】【牛客练习题】

一、题目描述 分子为1的分数称为埃及分数。现输入一个真分数(分子比分母小的分数,叫做真分数),请将该分数分解为埃及分数。如:8/11 = 1/2+1/5+1/55+1/110。 注:真分数指分子小于分母的分数,分子和分母有可能gcd不为1! 如有多个解,请输出任意一个。 二、输入描述 输…...

Zemax Lumerical | 二维光栅出瞳扩展系统优化

简介 本文提出并演示了一种以二维光栅耦出的光瞳扩展(EPE)系统优化和公差分析的仿真方法。 在这个工作流程中,我们将使用3个软件进行不同的工作 ,以实现优化系统的大目标。首先,我们使用 Lumerical 构建光栅模型并使用…...

Linux-0.11 文件系统read_write.c详解

Linux-0.11 文件系统read_write.c详解 模块简介 该模块实现了文件系统通用的读写的方法read/write/lseek。 根据文件类型的不同,在内部将调用不同的方法。如果是管道文件,则调用pipe.c中的读写方法,如果是字符设备,则会调用cha…...

什么是用户态和内核态?用户态切换内核态会有什么影响?

一、什么是用户态和内核态? 简单来讲,像使用java开发时,调用java中封装的普通方法程序时属于用户态,而操作内存或者cpu比如 new Thread()创建一个线程,Class.forName(xxx.class)这种属于内核态 用户态和内核态是操作系…...

探索iOS之CoreImage框架

CoreImage提供图像处理、人脸识别、图像增强、图像滤镜、图像转场。它操作的数据来自Core Graphics、Core Video、Image IO,使用CPU或GPU进行渲染。CoreImage对底层实现进行封装,为上层提供简单易用的API。 一、CoreImage框架 CoreImage框架分为&#…...

qml 使用Shape 画图形

最近在做项目的时候想这实现一个能够根据相对位置动态改变大小的进度条提示框,偶尔发现了一个很有用的组件Shape这个控件里面可以画各种线条,实线虚线矩形三角形圆角的三角形或者各种自定义形状。下面提供一个2条虚线加上一个矩形的小栗子。更多的自定义形状还是请自…...

MySQL数据库修改root账户密码

博主今天登录数据库遇到了一个问题,通过这篇文章(http://t.csdn.cn/58ECT)解决了。文中关于修改root账户密码的部分,博主觉得有必要写一篇文章总结下。 第一步:用管理员账户打开CMD 第二步:开启mysql服务 …...

基于springboot+Vue+ Element-Plus+mysql实现学生宿舍管理系统

基于springbootVue Element-Plusmysql实现学生宿舍管理系统 一、系统介绍二、功能展示1.登陆2、主页--学生3、主页--宿舍管理员4.学生管理--管理员5.宿管信息--管理员6.宿舍管理--管理员7.信息管理--管理员8.申请管理--管理员9.访客管理--管理员10.水电费管理--管理员11.卫生管…...

中国人才选拔制度演变

1、世官制 是西周时人们仍保持着牢固的宗族血缘联系,人群基本以族区分,并得到宗法封建制的制度上的保证,从而自然形成了各级宗族长同时也就是各级官长,家国一体、家国同构的统治模式、格局。换句话来讲就是我们所说的世袭制。 其…...

【JavaSE】Java基础语法(十六):抽象类

文章目录 1. 抽象类的概述2. 抽象类的特点3. 抽象类的实用价值4. 抽象类的案例 1. 抽象类的概述 当我们在做子类共性功能抽取时,有些方法在父类中并没有具体的体现,这个时候就需要抽象类了! 在Java中,一个没有方法体的方法应该定义…...

【Kafka】超详细介绍

文章目录 概念部署方案磁盘网络CPUpartition的数量 命令查看版本找kafka和zookeeper的ip/porttopic创建 topic查看get topic 列表get topic 详情 修改topic修改分区级别参数(如增加partition) 删除topic设置消息大小上限 生产查看生产生产消息 查看消费server 查看 offset查看积…...

2023 华为 Datacom-HCIE 真题题库 07/12--含解析

多项选择题 1.[试题编号:190187] (多选题)如图所示的拓扑采用了VXLAN分布式网关,SW1上的VBDIF10配置了:arp-proxy local enable命令,则以下描述中正确的有哪些项? A、SW1收到PC1发往PC2的报文&…...

Spring的作用域和生命周期

目录 1.Bean的作用域 2.Bean的作用域的分类 3.设置作用域 4.Spring的执行流程(生命周期) 5.Bean的生命周期 1.Bean的作用域 lombok (dependency依赖) 是为了解决代码的冗余(比如说get和set方法)那些构造…...

岭回归有看点:正则化参数解密,显著性不再成问题!

一、概述 「L2正则化(也称为岭回归)」 是一种用于线性回归模型的正则化方法,它通过在模型的损失函数中添加一个惩罚项来防止过拟合。L2正则化的惩罚项是模型参数的平方和,乘以一个正则化参数λ,即: L2正则化…...

Android 12.0修改recovery 菜单项字体大小

1.概述 在Android 12.0进入recovery模式后,界面会g_menu_actions 菜单选项和 提示文字,而这些文字的大小不像上层一样是通过设置属性来表示大小的 而它确是通过字体png图片的大小来计算文字的宽和高的,然后可以修改字体大小 2. 修改recovery 菜单项字体大小的核心类 buil…...

【计算机网络】 7、websocket 概念、sdk、实现

文章目录 一、背景二、简介三、client3.1 ws 构造函数3.2 ws.readyState3.3 ws.onopen3.4 ws.onclose3.5 ws.onmessage3.6 ws.send3.7 ws.bufferedAmount3.8 ws.onerror 四、server4.1 go4.1.1 apifox client4.1.2 js client 五、范式 一、背景 已经有了 http 协议&#xff0c…...

python中的常见运算符

文章目录 算数运算符赋值运算关系运算符逻辑运算符非布尔值的与或非运算条件运算符(也叫三元运算符)运算符的优先级 算数运算符 加法运算符(如果两个字符串之间进行加法运算,则会进行拼串操作) - 减法运算符 * 乘法运算符(如果将字…...

TypeScript类型

TypeScript 是什么? 是以avaScript为基础构建的语言个一JavaScript的超集。可以在任何支持JavaScript的平台中执行。TypeScript扩展了JavaScript,并添加了类型。TS不能被JS解析器直接执行,需要编译成js。 基本类型 声明完变量直赴进行赋值 let c: boo…...

Integer源码

介绍 Integer是int类型的包装类,继承自Number抽象类,实现了Comparable接口。提供了一些处理int类型的方法,比如int到String类型的转换方法或String类型到int类型的转换方法,当然也包含与其他类型之间的转换方法。 Comparable提供…...

【四】设计模式~~~创建型模式~~~建造者模式(Java)

【学习难度:★★★★☆,使用频率:★★☆☆☆】 4.1. 模式动机 无论是在现实世界中还是在软件系统中,都存在一些复杂的对象,它们拥有多个组成部分,如汽车,它包括车轮、方向盘、发送机等各种部件…...

MarkDown的基本使用方法

为了给官方的文档知识总结:Markdown 基本语法 | Markdown 官方教程 #空格内容:‘#’表示标题的等级,越少表示标题级别越高(字越大) 在一行的末尾加两个或多个空格再回车,就是我们普通的文本回车。【还有一…...

IDEA 安装配置步骤详解

引言 IntelliJ IDEA 是一款功能强大的集成开发环境,它具有许多优势,适用于各种开发过程。本文将介绍 IDEA 的主要优势,并提供详细的安装配置步骤。 介绍 IntelliJ IDEA(以下简称 IDEA)之所以被广泛使用,…...

【网络】· 路由器中配置单臂路由和DHCP,VTP原理

目录 🍉单臂路由的工作原理 🥝交换机配置 🥝路由器配置 🍉路由器配置DHCP 🥝配置实例 🥝路由器配置 🥝验证 🍉VTP工作原理 🥝VTP模式 🥝VTP通告 &#x1f95d…...

Python 子域名扫描工具:使用多线程优化

部分数据来源:ChatGPT 本文仅用于信息安全的学习,请遵守相关法律法规,严禁用于非法途径。若观众因此作出任何危害网络安全的行为,后果自负,与本人无关。 摘要:子域名扫描是一个重要的安全工作,它可以发现目标网站的更多威胁和漏洞。本文介绍了如何使用 Python 来编写一…...