数据复制的艺术:深拷贝与浅拷贝在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的使用多轮对话,流式输出,微调,知识向量库等知识之后,接下来需要进一步补足的一个大块就是提示词工程,学习和了解提示词工程除了基本的提…

file_operations

unlocked_ioctl 与 compat_ioctl 的区别 .unlocked_ioctl spidev_ioctl, 32 位 程序 ->32位 kernel 64 位 程序 ->64位 kernel .compat_ioctl spidev_compat_ioctl, 32 位 程序 ->64位 kernel 测试流程: static const struct file_operations spidev_fops …

利用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…

【Spring AI】前言

文章目录 Spring AI Spring AI 该文档是翻译 Spring Ai 英文文档 官方文档:https://docs.spring.io/spring-ai/reference/index.html 版本:0.8.1 翻译内容: 基本内容都会翻译涉及到 AI 模型的章节,由于本人资金有限&#xff0c…

PostgreSQL的扩展(extensions)-常用的扩展之auto_explain

PostgreSQL的扩展(extensions)-常用的扩展之auto_explain auto_explain 是 PostgreSQL 中的一个扩展模块,它可以用来自动记录执行计划以帮助分析查询性能问题。当启用时,这个模块会记录超过指定执行时间的所有查询的执行计划&…

JavaScript简介及未来发展

JavaScript 是一种高级的、解释型的编程语言。它是一种基于原型、多范式的动态脚本语言,广泛应用于网页开发中,用于增强网页的交互性、创建动态更新的内容、动画等,并且能够响应用户的一,来龙去脉 JavaScript 最初由 Netscape 公司…

【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、编程实现 在小学奥数中经常会看到一些填数字的游戏,如下图所示,其中每个…