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

news2024/11/19 7:47:56

详解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 = obj
	obj2.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 = 2

console.log(obj)
console.log(obj2); 

obj.b.c = 2

console.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、应用场景

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

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

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

相关文章

25 strlen 的调试

前言 同样是一个 很常用的 glibc 库函数 不管是 用户业务代码 还是 很多类库的代码, 基本上都会用到 字符串长度的计算 不过 我们这里是从 具体的实现 来看一下 它的实现 主要是使用 汇编 来进行实现的, 因此 理解需要一定的基础 测试用例 就是简单的使用了一下 strc…

Vscode搭建开发调试STM32/RISC-V环境IDE(最全面)

单片机开发IDE环境如KeilMDK,虽然操作简单,方便调试。但就是代码编辑风格很老套,中文符号乱码还是常有的事。而如今流行的vscode编辑器很不错,免费且相当轻量级,用来代码开发体验很不错,看着都舒服。Clion …

【数学建模实例之SEIR】

学习数学建模: 从基础到实践 引言 在我们日常生活中,数学建模(Mathematical Modeling)是一个非常重要的工具,它帮助我们理解复杂的问题,并找到解决这些问题的方法。在这篇博客中,我们将探讨数学建模的基本…

深入理解计算机系统——汇编基础

文章目录 寄存器数据格式mov操作 push,popcall,retleave,enter算术和逻辑操作一元操作二元操作移位操作 特殊的算术操作控制条件码访问条件码跳转很好的例题 翻译条件分支循环条件传送指令switch例 函数堆栈递归的过程 数组数据结构结构体联合 使用GDB调…

信息与编码SCUEC DDDD 期末复习整理(1)

1.1948年,美国数学家香农发表了题为“通信的数学原理”的论文,从而创立了信息论。 2.不可能事件的自信息量是(∞),必然事件的自信息量是(0) 3. 4.差错控制的主要方式有前向纠错方式FEC&#x…

线程的创建(Runnable,Future,CompletionService,CompletableFuture的辨析)

直接使用Thread 直接让某个类继承Thread类,复写run方法,外部调用的时候直接调用start方法。 因为java的单继承模式,但是我们一般不直接使用这种方法。 使用Runnable Slf4j public class MyTask implements Runnable {Overridepublic void …

Vue+springboot餐厅美食菜品评价系统4d5g9

餐厅是一个传统的行业。随着当今社会的发展,时代的进步,行业也在发生着变化,单就点菜这一方面,菜品评价正在逐步进入人们的生活。传统的菜品评价,不仅会耗费大量的人力、时间,有时候还会出错。网上可以解决…

ORTP库局域网图传和VLC实时预览

​ 1.ORTP的引入 1.1、视频网络传输的2种方式 (1)基于下载:http or ftp(网站播放视频,追求清晰度,哪怕时间晚一点) (2)基于实时:RTP/RTSP/RTCP(直播、监控,追求实时,…

Linux 实操篇-组管理和权限管理

Linux 实操篇-组管理和权限管理 Linux 组基本介绍 在linux 中的每个用户必须属于一个组,不能独立于组外。在linux 中每个文件有所有者、所在组、其它组的概念。 所有者所在组其它组改变用户所在的组 文件/目录所有者 一般为文件的创建者,谁创建了该文件&#x…

First Order Motion Model for Image Animation 笔记

First Order Motion Model for Image Animation 摘要 Image animation consists of generating a video sequence so that an object in a source image is animated according to the motion of a driving video. Our framework addresses this problem without using any a…

表情识别(从原理到代码安装)

1. 项目介绍 面青识别(face_classification )是一个基于深度学习的面部表情识别项目,它使用 Keras 和 TensorFlow 框架来实现模型的训练和预测。该项目的主要目标是在图像或视频中检测并识别人脸表情,并将其分类为七种不同的情绪类别:生气、厌恶、害怕、高兴、平静、伤心…

JVM学习笔记(完结)

类加载与字节码技术 1、类文件结构 通过 javac 类名.java 编译 java 文件后,会生成一个 .class 的文件! 以下是字节码文件: 0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09 0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00…

全面大涨原因!多家基金解读

周五!大涨! 6月2日,A股以强势反弹结束本周的交易,整体全面上行,几乎所有主流指数都收涨。沪指高开高走,深成指、创业板指涨超1%。总体来看,个股涨多跌少,两市超3300股处于上涨状态。…

Linux4.4网页与安全优化

文章目录 计算机系统5G云计算第一章 LINUX Apache网页与安全优化一、网页压缩1.检查是否安装 mod_deflate 模块2.如果没有安装mod_deflate 模块,重新编译安装 Apache 添加 mod_deflate 模块3.配置 mod_deflate 模块启用4.检查安装情况,启动服务5.测试 mo…

redis第三章-redis集群redisCluster

1.redis集群模式比较 (1)哨兵模式 哨兵模式是利用哨兵来做主从切换的,当主节点发生故障的时候,通过哨兵去选取出一个从节点作为主节点,但本身哨兵的配置还是有些麻烦,并且实际上哨兵的性能和高可用性一般…

chatgpt赋能python:使用Python创建结构体:完全指南

使用Python创建结构体:完全指南 在Python编程领域,结构体是一种非常方便和有用的数据类型,用于存储和组织相关变量。在本篇文章中,我们将讨论如何使用Python创建结构体。让我们开始吧! 什么是结构体? 结…

shell学习

1、/etc/hosts的作用 Windows下的目录C:\Windows\System32\drivers\etc\hosts Linux下目录/etc/hosts 如 我们在/etc/hosts文件中添加一行 39.156.66.10 taobao.com 原理是,我们在浏览器输入 taobao.com,那么网站就可以打开百度的网站 但是现实是网…

7大常用ES6特性,助力你写出更现代化的JavaScript

文章目录 1. 模板字符串2. 箭头函数3. let 和 const4. 解构赋值5. 函数默认参数6. 模块化7. Promise 1. 模板字符串 模板字符串是一种新的字符串类型,它允许你在字符串中插入变量,方便了JavaScript开发者的开发体验。 ES6的模板字符串(Templa…

demo:搜索帮助出口

写报表,用到搜索帮助,太久不写了,忘了,然后简单测了下。 当然方法很多,我只是突然想起这个东西来了,就测了下,条条大路通北京,想咋实现就咋实现吧,实现了就得了~ 代码很简…

chatgpt赋能python:Python代码教你删除空文件夹——让你的电脑系统更健康

Python代码教你删除空文件夹——让你的电脑系统更健康 作为一名有着10年python编程经验的工程师,我发现在电脑里存储着太多的空文件夹时会对电脑系统造成负面影响。空文件夹占用了硬盘空间,这可能导致你的电脑运行缓慢或者存储空间不足。因此&#xff0…