js数组/对象的深拷贝与浅拷贝
文章目录
- 一、js中的深拷贝和浅拷贝
- 二、浅拷贝
- 1、Object.assign()
- 2、利用es6扩展运算符(...)
- 二、深拷贝
- 1、JSON 序列化和反序列化
- 2、js原生代码实现
- 3、使用第三方库lodash等
- 四、总结
一、js中的深拷贝和浅拷贝
在JS中,深拷贝和浅拷贝是针对对象(Object)和数组(Array)这类复杂数据类型复制时的概念。
-
浅拷贝: 当进行浅拷贝时,只是将对象或数组的引用复制一份给新的变量。这意味着新旧变量指向的是同一个内存空间中的数据结构,修改其中一个变量会影响到另一个变量,因为它们实际上是共享同一份数据。
-
深拷贝: 则是创建一个与原对象完全独立的对象副本,对于原对象内部所包含的所有层级的数据(包括嵌套的对象或数组),都会递归地进行复制,从而保证了新旧对象间不存在任何共享的引用。
简单来说,对拷贝后的数组或对象进行修改,会影响原数组或对象的就是浅拷贝,不会影响的就是深拷贝。
二、浅拷贝
1、Object.assign()
Object.assign() 基本用法:将一个或多个源对象的属性复制到目标对象,并返回目标对象。这一过程是浅拷贝的,即对于嵌套对象或数组,只是拷贝了引用而非创建新的对象。
示例:
const obj = {a: 1,b: {c: 2},d: ["e"]
};
const cloneObj = Object.assign({}, obj);
cloneObj.b.c = 3; // 修改拷贝后的对象console.log("obj: ", obj);
console.log("cloneObj: ", cloneObj);
结果:

从结果可以看到,修改 clonedObj 的属性也会影响到原始对象 obj,所以Object.assign()实现的是浅拷贝。
2、利用es6扩展运算符(…)
在ES6中,...运算符被称为扩展(Spread)运算符,可以用于数组和对象的浅拷贝。
示例1:拷贝对象
const obj2 = {a: 1,b: {c: 2},d: ["hello"]
};
const cloneObj2 = { ...obj2 };
cloneObj2.b.c = 2222222;console.log("obj2: ", obj2);
console.log("cloneObj2: ", cloneObj2);
结果:

示例2:拷贝数组
const arr = ["1", { a: 2 }, 3, true, undefined];
const arrClone = [...arr];
arrClone[0] = "first"; // 修改数组第1个元素
arrClone[1].a = "hello"; // 修改数组第2个元素console.log("arr: ", arr);
console.log("arrClone: ", arrClone);
结果:

从...拷贝数组结果来看,原始数组和拷贝数组是独立的,但由于数组中包含的对象引用并未改变,所以修改拷贝数组中对象的属性时,原始数组中对应的对象也会受到影响。
也就是说,对于简单数组,...可以实现数组深拷贝,但是复杂数组…则不能用做实现深拷贝的方法。
二、深拷贝
1、JSON 序列化和反序列化
利用 JSON 的序列化和反序列化可以实现对象的深拷贝。原理是:JSON.stringify()会递归遍历对象的所有属性(包括嵌套的对象和数组),将其转换为JSON字符串;然后JSON.parse()则会根据JSON字符串创建新的JavaScript对象。
示例:
const obj1 = {a: 1,b: {c: 2},d: ["hello"]
};
const cloneObj1 = JSON.parse(JSON.stringify(obj1));
cloneObj1.b.c = 3;console.log("obj1: ", obj);
console.log("cloneObj1: ", cloneObj);
结果:

从结果看到,修改拷贝后的对象其中的属性值,并不会改变原对象的属性值,所以实现的是一个深拷贝。
!!!注意这种方法有局限:
- 它不能处理函数和RegExp等非JSON兼容类型。
- 对象中的循环引用会导致错误或丢失数据。
- 如果对象中有undefined、function或symbol类型的属性,它们在序列化过程中会被忽略。
- 对日期对象,JSON.stringify会将日期转换为字符串,所以反序列化后得到的是字符串而不是Date对象,如果需要保持日期类型,需要额外处理。
2、js原生代码实现
- 简单版:只考虑普通对象属性,不考虑内置对象和函数
function deepClone1(obj) {if (typeof obj !== "object") return;let newObj = obj instanceof Array ? [] : {};for (let key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] =typeof obj[key] === "object" ? deepClone1(obj[key]) : obj[key];}}return newObj;}// 测试
const obj5 = {a: 1,b: {c: 2},d: ["hello"],e: new Date(),f: new Error("error"),g: new RegExp("/^(.*\..{4}).*$/")
};
const cloneObj5 = deepClone1(obj5);
cloneObj5.b.c = "hhhhhh";console.log("obj5: ", obj5);
console.log("cloneObj5: ", cloneObj5);
结果:

- 【推荐】复杂版:考虑内置对象比如Date、RegExp等对象和函数以及解决循环引用的问题。
// 判断是否为object类型
function isObject(target) {return ((typeof target === "object" && target) || typeof target === "function");}
function deepClone(data, map = new WeakMap()) {// 基础类型直接返回值if (!isObject(data)) {return data;}// 日期或者正则对象则直接构造一个新的对象返回if ([Date, RegExp].includes(data.constructor)) {return new data.constructor(data);}// 处理函数对象if (typeof data === "function") {return new Function("return " + data.toString())();}// 如果该对象已存在,则直接返回该对象const exist = map.get(data);if (exist) {return exist;}// 处理Map对象if (data instanceof Map) {const result = new Map();map.set(data, result);data.forEach((val, key) => {// 注意:map中的值为object的话也得深拷贝if (isObject(val)) {result.set(key, deepClone(val));} else {result.set(key, val);}});return result;}// 处理Set对象if (data instanceof Set) {const result = new Set();map.set(data, result);data.forEach(val => {if (isObject(val)) {// 注意:set中的值为object的话也得深拷贝result.add(deepClone(val));} else {result.add(val);}});return result;}// 收集键名(考虑了以Symbol作为key以及不可枚举的属性)const keys = Reflect.ownKeys(data);// 利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性以及对应的属性描述const allDesc = Object.getOwnPropertyDescriptors(data);// 结合 Object 的 create 方法创建一个新对象,并继承传入原对象的原型链, 这里得到的result是对data的浅拷贝const result = Object.create(Object.getPrototypeOf(data), allDesc);// 新对象加入到map中,进行记录map.set(data, result);// Object.create()是浅拷贝,所以要判断并递归执行深拷贝keys.forEach(key => {const val = data[key];if (isObject(val)) {// 属性值为 对象类型 或 函数对象 的话也需要进行深拷贝result[key] = deepClone(val);} else {result[key] = val;}});return result;
}// 测试
const obj4 = {a: 1,b: {c: 2},d: ["hello"],e: new Date(),f: new Error("error"),g: new RegExp("/^(.*\..{4}).*$/")
};
const cloneObj4 = deepClone(obj4);
cloneObj4.b.c = "hhhhhh";console.log("obj4: ", obj4);
console.log("cloneObj4: ", cloneObj4);
结果:

3、使用第三方库lodash等
例如:import { cloneDeep } from 'lodash'; 这种拿来就可以用的方法就不做过多介绍了~
四、总结
总的来说,js中的深、浅拷贝其实就是一个基本功,在实际业务场景中,可根据数据复杂程度自行选择各种实现方式,加油⛽️
相关文章:
js数组/对象的深拷贝与浅拷贝
文章目录 一、js中的深拷贝和浅拷贝二、浅拷贝1、Object.assign()2、利用es6扩展运算符(...) 二、深拷贝1、JSON 序列化和反序列化2、js原生代码实现3、使用第三方库lodash等 四、总结 一、js中的深拷贝和浅拷贝 在JS中,深拷贝和浅拷贝是针对…...
HCIA学习第六天:OSPF:开放式最短路径优先协议
OSPF:开放式最短路径优先协议 无类别链路状态IGP动态路由协议 1.距离矢量协议:运行距离矢量协议的路由器会周期性的泛洪自己的路由表。通过路由的交互,每台路由器从相邻的路由器学习到路由,并且加载进自己的路由表中;…...
从四个方面来解决企业在项目管理中遇到的各类问题
案例背景:某建筑集团有限公司成立于1949年,拥有国家房屋建筑工程施工总承包一级、建筑装修装饰工程专业承包一级、市政公用工程施工总承包一级资质。是一家集建筑施工、设备安装、装饰装潢、仿古建筑、房地产开发、建材试验为一体的具有综合生产能力的建…...
使用代码取大量2*2像素图片各通道均值,存于Excel文件中。
任务是取下图RGB各个通道的均值及标签(R, G,B,Label),其中标签由图片存放的文件夹标识。由于2*2像素图片较多,所以将结果放置于Excel表格中,之后使用SVM对他们进行分类。 from PIL import Image import os …...
React16源码: React中commit阶段的commitBeforeMutationLifecycles的源码实现
commitBeforeMutationLifecycles 1 )概述 在 react commit 阶段的 commitRoot 第一个while循环中调用了 commitBeforeMutationLifeCycles现在来看下,里面发生了什么 2 )源码 回到 commit 阶段的第一个循环中,在 commitRoot 函数…...
压制二元组的总价值
压制二元组的总价值 对于每一个 a i a_i ai, 看它能压制它前面的多少个元素, 那么它对总价值的贡献就是: 在a数组中: a i a_i ai压制了x个数, 贡献为: x ∗ i x*i x∗i被 a i a_i ai所压制的所有数在 a a a中的下标和为 y y y, 贡献为 − y -y −y 树状数组来求: 为了…...
【习题】保存应用数据
判断题 1. 首选项是关系型数据库。 错误(False) 2. 应用中涉及到Student信息,如包含姓名,性别,年龄,身高等信息可以用首选项来存储。 错误(False) 3. 同一应用或进程中每个文件仅存在一个Preferences实例。 正确(True) 单选题 …...
Flask框架小程序后端分离开发学习笔记《5》简易服务器代码
Flask框架小程序后端分离开发学习笔记《5》 Flask是使用python的后端,由于小程序需要后端开发,遂学习一下后端开发。 简易服务器代码 接口解析那一块很关键,学后端服务器这一块,感觉主要就是学习相应地址的接口怎么处理。 然后…...
“计算机视觉处理设计开发工程师”专项培训(第二期)
“人工智能技术与咨询” 发布...
R语言学习case7:ggplot基础画图(核密度图)
step1: 导入ggplot2库文件 library(ggplot2)step2:带入自带的iris数据集 iris <- datasets::irisstep3:查看数据信息 dim(iris)维度为 [150,5] head(iris)查看数据前6行的信息 step4:画图展示 plot2 <- ggplot(iris,aes(Sepal.W…...
Ubuntu18配置Docker
1.基本过程 1.更新软件源列表 sudo apt update2.安装软件包依赖 sudo apt install apt-transport-https ca-certificates curl software-properties-common3.在系统中添加Docker的官方密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - …...
Keil/MDK平台 - 结构体成员指针注意事项
文章目录 1 . 前言总结2 . 问题现象3 . 解决思路4 . 细节扩展5 . 总结 【极客技术传送门】 : https://blog.csdn.net/Engineer_LU/article/details/135149485 1 . 前言总结 有时候希望通过类定义的类型指向数据包来解析,恰好又想结构体内定义指针指向一段数据&…...
一款超级好用的远程控制APP,你值得拥有
在这个科技日新月异的时代,我们的生活被各种手机软件所包围。几乎每个人都有一个甚至多个手机,你是否也有遇到过需要远程操作自己某一台手机的场景呢?今天,我要向大家推荐一款神奇的手机远程操作神器,让你可以随时随地…...
NumPy必知必会50例 | 18. 使用 NumPy 解决线性方程组:数学问题的实用解决方案
继续我们的 NumPy 探索之旅吧,接下来我们将探讨使用 NumPy 解决线性方程组,一种实用的数学应用。 文章目录 18. 使用 NumPy 解决线性方程组:数学问题的实用解决方案线性方程组:数学世界的基石创建线性方程组 解决实际问题应用场景…...
C/C++编码问题研究
文章目录 一、Unicode字符集与U8/U16/U32编码二、编码1. 占字节数2. ASCII、GB2312、GBK、GB18030 以及 UTF8 的关系3. BOM4. UTF-8的存储实现 三、编译器字符集设置1. GCC语法Example 2. MSVC语法Example 三、wchar_t五、编码转换函数六、代码 & 实践1. UTF8与UTF16、UTF3…...
二刷代码随想录|Java版|回溯算法3|子集问题
习题 2.3 子集问题 就是组合过程收集path。就像是代码随想录里说得那样,组合和分割问题就是收集叶子结点,子集问题就是收集每一个节点。 有涉及到同层重复元素的问题。 先排序,后再for循环里处理相同数值跳过。 设置函数内的used。 还可以用…...
mongodb config
windows: 1.同级bin,data,log创建mongo.config文件 dbpathD:\Program\mongodb\data\db logpathD:\Program\mongodb\log\mongo.log logappendtrue #默认启用日志 journaltrue #过滤无用日志信息,调试设置为false quiettrue port2…...
pytorch 实现中文文本分类
🍨 本文为[🔗365天深度学习训练营学习记录博客🍦 参考文章:365天深度学习训练营🍖 原作者:[K同学啊 | 接辅导、项目定制]\n🚀 文章来源:[K同学的学习圈子](https://www.yuque.com/mi…...
【MySQL】聚合函数和内置函数
文章目录 1 :peach:聚合函数:peach:2 :peach:group by子句的使用:peach:3 :peach:内置函数:peach:3.1 :apple:日期函数:apple:3.2 :apple:字符串函数:apple:3.3 :apple:数学函数:apple: 4 :peach:其它函数:peach: 1 🍑聚合函数🍑 函数说明COUNT([DISTIN…...
python第五节:集合set(4)
集合其他方法: len(s) set 的长度 x in s x 是否是 s 的成员 x not in s x 是否不是 s 的成员 s.issubset(t) 是否 s 中的每一个元素都在 t 中 s.issuperset(t) 是否 t 中的每一个元素都在 s s.union(t) 返回一个新的 set 包含 s 和 t 中的每一个元素 …...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...
GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
