详解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)基本数据类型:将值存储在栈中 ,栈中存放的是对应的值
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正则化…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

