数据复制的艺术:深拷贝与浅拷贝在JavaScript中的实现方式

前言

 📫 大家好,我是南木元元,热爱技术和分享,欢迎大家交流,一起学习进步!

 🍅 个人主页:南木元元


目录

赋值和拷贝

浅拷贝与深拷贝区别

浅拷贝的实现方式

1.Object.assign()

2.扩展运算符

3.Array.prototype.slice()

4.Array.prototype.concat()

5.手写实现浅拷贝

深拷贝的实现方式

1.JSON.stringify()

2.函数库lodash

3.手写实现深拷贝

结语


赋值和拷贝

js中的数据类型分为两大类:

  • 基本数据类型
  • 引用数据类型

基本数据类型保存在里面,可以直接访问它的值,赋值时,会完整复制变量值。

let a = 10;
let b = a; // 赋值
b = 20;
console.log(a); // 10

上面代码中,两个变量保存了两个不同的内存地址,所以b的改变不会引起a的变化。过程如下:

而引用数据类型保存在里面,栈里面保存的是地址,通过栈里面的地址去访问堆里面的值。

var obj1 = {}
var obj2 = obj1;
obj2.name = "yuanyuan";
console.log(obj1.name); // yuanyuan

 上面代码在赋值时,复制的是栈中的引用地址,两个变量指向堆内存中的同一个对象,所以更改obj2会对obj1产生影响。赋值过程如下:

这是直接赋值的情况,要想两个值互不影响,就需要进行拷贝,js中的拷贝分为浅拷贝和深拷贝。

浅拷贝与深拷贝区别

浅拷贝是按位拷贝对象,它会创建一个新对象,如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址(只拷贝一层)。如果其中一个对象改变了这个地址,就会影响到另一个对象。

深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

浅拷贝的实现方式

1.Object.assign()

Object.assign()方法用于将所有可枚举的自有属性从一个或多个源对象复制到目标对象,并返回这个目标对象。可以使用它来实现浅拷贝。

// 初始对象
const obj = {name: 'yuanyuan',des: {age: 21,gender: 'male'}
};// 使用 Object.assign 进行浅拷贝
const obj2 = Object.assign({}, obj);// 修改原始对象中的属性
obj.name = '元元';
obj.des.age = 22;// 输出
console.log(obj); // { name: '元元', des: { age: 22, gender: 'male' } }
console.log(obj2); // { name: 'yuanyuan', des: { age: 22, gender: 'male' } }

在上述示例中,Object.assign()创建了一个新对象 obj2并复制了obj中的所有属性。修改obj.name 只影响初始对象obj,不会影响拷贝后的对象,因为字符串是基本数据类型,被复制的是值。

但是,对于初始对象obj的des的修改影响了拷贝后对象obj2的des,因为对象的des属性是一个对象,而Object.assign()复制的是这个对象的引用。因此,当修改obj.des.age时,由于obj.des和obj2.des引用的是同一个对象,所以拷贝后的对象obj2中的des也会被改变。

从这个例子可以看到,浅拷贝是按位拷贝对象,仅拷贝一层,适用于那些不包含深层次嵌套对象的情况。

2.扩展运算符

扩展运算符是一个es6的特性,它提供了一种非常方便的方式来执行浅拷贝,与object.assign实现的浅拷贝相同。

let obj1 = {a:1,b:{c:1}}
let obj2 = {...obj1};
obj1.a = 2;
console.log(obj1); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}
obj1.b.c = 2;
console.log(obj1); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}

3.Array.prototype.slice()

Array.prototype.slice()方法可以从已有数组中返回选定的元素,不会改变原始数组。

let arr = [1, 2, 3];
let newArr = arr.slice();
newArr[1] = 100;
console.log(arr);//[ 1, 2, 3 ]

4.Array.prototype.concat()

Array.prototype.concat()方法用于合并两个或多个数组,此方法不会更改原始数组,而是返回一个新数组。

let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr);//[ 1, 2, 3 ]

5.手写实现浅拷贝

实现浅拷贝的思路:

  • 对基础类型做最基本的拷贝;

  • 对引用类型开辟新的存储,并且拷贝一层对象属性。

//实现浅拷贝
function shallowCopy (obj){// 只拷贝对象,基本类型或null直接返回if(typeof obj !== 'object' || obj === null) {return obj;}// 判断是新建一个数组还是对象let newObj = Array.isArray(obj) ? []: {};//for…in会遍历对象的整个原型链,如果只考虑对象本身的属性,需要搭配hasOwnPropertyfor(let key in obj ){//hasOwnProperty判断是否是对象自身属性,会忽略从原型链上继承的属性if(obj.hasOwnProperty(key)){newObj[key] = obj[key];//只拷贝对象本身的属性}}return newObj;
}

深拷贝的实现方式

1.JSON.stringify()

JSON.parse(JSON.stringify());

JSON.parse(JSON.stringify(obj))方法是常用的深拷贝方法之一,它的原理就是利用JSON.stringify将JavaScript对象序列化成为JSON字符串,并将对象里面的内容转换成字符串,再使用JSON.parse来反序列化,将字符串生成一个新的JavaScript对象。

let obj1 = {  a: 0,b: {c: 0}
};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}

这个方法虽然简单,但也存在一些问题:

  • ⽆法解决循环引用问题
  • 无法拷贝一些特殊的对象,如 RegExp (会变成空对象)、 Date (被转成字符串)
  • 无法拷贝函数
function Obj() {this.func = function () { alert(1) };this.obj = {a:1};this.arr = [1,2,3];this.und = undefined;this.reg = /123/;this.date = new Date(0);this.NaN = NaN;this.infinity = Infinity;this.sym = Symbol(1);
}
let obj1 = new Obj();
Object.defineProperty(obj1,'innumerable',{enumerable:false,value:'innumerable'
});
console.log('obj1',obj1);
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
console.log('obj2',obj2);

结果如下: 

2.函数库lodash

可以使用一些第三方库如lodash,它提供了_.cloneDeep用来做深拷贝,可以直接引入并使用:

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

3.手写实现深拷贝

实现深拷贝的思路就是,使用for in来遍历传入参数的属性值,如果值是基本类型就直接复制,如果是引用类型就进行递归调用该函数,实现代码如下:

function deepCopy (obj, map = new WeakMap()){// 基本类型或null直接返回if(typeof obj !== 'object' || obj === null) {return obj;}if (obj instanceof Date) return new Date(obj);if (obj instanceof RegExp) return new RegExp(obj);// 判断是新建一个数组还是对象let newObj = Array.isArray(obj) ? []: {};//利用map解决循环引用if (map.has(obj)) {return map.get(obj);}map.set(obj, newObj);//将当前对象作为key,克隆对象作为valuefor(let key in obj ){	if(obj.hasOwnProperty(key)){newObj[key] = deepCopy(obj[key], map);	//递归}}return newObj
}

结语

🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下博主~ 

 

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

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

相关文章

【bug已解决】发生错误,导致虚拟 CPU 进入关闭状态。如果虚拟机外部发生此错误,则可能已导致物理计算机重新启动......

本bug报错已找到原因,并成功解决。 项目场景: vmware安装ubuntu报错。 如下: 发生错误,导致虚拟 CPU 进入关闭状态。如果虚拟机外部发生此错误,则可能已导致物理计算机重新启动。错误配置虚拟机、客户机操作系统中的错误或 VMware Workstation 中的问题都可以导致关闭状…

Docker有哪些常见命令?什么是Docker数据卷?

喜欢就点击上方关注我们吧! 哈喽,大家好呀!这里是码农后端。上一篇我们介绍了Docker的安装以及腾讯云镜像加速源的配置。本篇将带你学习Docker的常见命令、数据卷及自定义镜像等相关知识。 1、什么是镜像与容器? 利用Docker安装应…

提示词工程入门-使用文心一言4.0-通义千问-GPT4-Claude3通用提示技巧测试

提示词工程基础🚀 在了解完了大语模型的基本知识,例如API的使用多轮对话,流式输出,微调,知识向量库等知识之后,接下来需要进一步补足的一个大块就是提示词工程,学习和了解提示词工程除了基本的提…

利用GaussDB的可观测性能力构建故障模型

D-SMART高斯专版已经开发了几个月了,目前主要技术问题都已经解决,也能够初步看到大概的面貌了。有朋友问我,GaussDB不已经有了TPOPS了,为什么你们还要开发D-SMART高斯专版呢? 实际上TPOPS和D-SMART虽然都可以用于Gaus…

实验案例二:配置Trunk,实现相同VLAN的跨交换机通信

1.实验环境 公司的员工人数已达到100人,其网络设备如图12.13所示。现在的网络环境导致广播较多 网速慢,并且也不安全。公司希望按照部门划分网络,并且能够保证一定的网络安全性 其网络规划如下: PC1和 PC3为财务部,属于 VLAN 2&…

MySQL库表占用空间排序

在进行数据库备份恢复时,经常会碰到耗时很长的问题。大概率是因为某些库表的占用空间太大。 以下语句按照库表占用空间大小,进行降序排序: SELECT table_schema AS Database,table_name AS Table,ROUND((data_length index_length) / 1024…

Json数据概念及C# 环境下的序列化、反序列化操作

什么是Json? JSON的全称是JavaScript Object Notation,是一种轻量级的数据交换格式,主要用于数据的序列化和交互。常用于Web中,其他领域也经常出现JSON的身影。 与xml相比,更快,更小,更容易解析…

Linux搭建局域网私有yum仓库/配置本地光盘镜像仓库/搭建公有yum仓库--7700字详谈

帮助与补全功能 1.补全 yum (options)COMMAND check check-update clean deplist downgrade erase fs fssnapshot groups help history info install list makecache provides reinstall repo-pkgs repolist search shell swap update update-minimal …

【数学建模】DVD在线租赁

2005高教社杯全国大学生数学建模竞赛题目B 随着信息时代的到来,网络成为人们生活中越来越不可或缺的元素之一。许多网站利用其强大的资源和知名度,面向其会员群提供日益专业化和便捷化的服务。例如,音像制品的在线租赁就是一种可行的服务。这…

如何有效的将丢失的mfc140u.dll修复,几种mfc140u.dll丢失的解决方法

当你在运行某个程序或应用程序时,突然遭遇到mfc140u.dll丢失的错误提示,这可能会对你的电脑运行产生一些不利影响。但是,不要担心,以下是一套详细的mfc140u.dll丢失的解决方法。 mfc140u.dll缺失问题的详细解决步骤 步骤1&#x…

【TDengine】mac m1解决no taos in java.library.path

前言 使用macos搭建springbootmybatisplus,通过mqtt将数据更新到tdenigne 3.2.3,数据源使用远程服务器的tdengine。 问题 启动时报错: Caused by: java.lang.UnsatisfiedLinkError: no taos in java.library.path 以下是官方文档 打开本…

华为校招机试 - 满二叉搜索树查找(20240424)

在线OJ测试 题目详情 - 满二叉搜索树查找 - HydroOJ 题目描述 给定 (2^n) - 1 个不同的整数(1 ≤ n ≤ 10,n 为整数),构建一棵平衡满二叉搜索树。 二叉搜索树定义如下: 节点的左子树只包含小于当前节点的数节点的右子树只包含大于当前节点的数所有左子树和右子树自身必…

RCE复习(ctfhub下)

先了解一下命令注入的知识点: 知识点 1、常见的拼接符 A ; B 先执行A,再执行BA & B 简单的拼接A | B 显示B的执行结果A&&B A执行成功之后才会执行BA || B A执行失败之后才会执行B , 在特殊情况下可代替空格…

算法学习002-填数游戏 中小学算法思维学习 信奥算法解析 c++实现

目录 C填数游戏 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、推荐资料 C填数游戏 一、题目要求 1、编程实现 在小学奥数中经常会看到一些填数字的游戏,如下图所示,其中每个…

【PostgreSQL】pg触发器介绍

注: 本文为云贝教育 刘峰 原创,请尊重知识产权,转发请注明出处,不接受任何抄袭、演绎和未经注明出处的转载。 触发器是在对指定表执行指定更改操作(SQL INSERT、UPDATE、DELETE 或 TRUNCATE 语句)时自动运行的一组操作…

3个IE还不够?誉天大牛学员:我要学5个!

乔同学 誉天4HCIERHCA学员 2018年盛夏,在那个充满了炙热与躁动的青春里,因为考HCIA-R&S,我和誉天相遇了。我想,大概是从那个时候开始,命运的齿轮便发生了转动,为我打开了通往ICT的大门。 时至今日&…

vue2左侧菜单栏收缩展开功能

目录 1. Main.vue页面代码 a. 修改侧边栏属性 b. 修改头部导航栏 c. 定义我们的变量 d. collapse函数 2. Header.vue页面代码 3. Aside.vue页面代码 vue2左侧菜单栏收缩展开目前是非常常见的,我们在日常开发过程中经常会碰到。这一小节我们就详细了解一下这个…

如何使用PHPMyAdmin进行数据库备份

本周有一个客户,购买Hostease的虚拟主机,询问我们的在线客服,如何使用PHPMyAdmin进行数据库备份。我们为用户提供教程,用户很快完成了设置。在此,我们分享这个操作教程,希望可以对您有帮助。 它适用于安装P…

笔试刷题-Day10

牛客 一、DP30买卖股票的最好时机(一) 算法:虽然题目标了DP但是用贪心更快页更容易理解 import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main {public static void main(String[] args) {Sca…