【吃透Js】深入学习浅拷贝和深拷贝
- 一、JavaScript数据类型
- 原始类型
- 对象类型
- 二、原始类型和对象类型的区别
- 1.原始类型
- 2.引用类型
- 3.复制
- 4.比较
- 5.值传递
- 三、浅拷贝
- 概念
- 实现方法
- 四、深拷贝
- 概念
- 五、浅拷贝、深拷贝和赋值的区别
- 浅拷贝和赋值
- 六、小结
想要真正搞明白深浅拷贝,你必须要熟练掌握赋值、对象在内存中的存储、数据类型等基础知识。
为了更好地掌握深浅拷贝,我们先来看一下数据类型和在内存中的存储形式。
一、JavaScript数据类型
原始类型
Null
:只包含一个值:null
Undefined
:只包含一个值:undefined
Boolean
:包含两个值:true
和false
Number
:整数或浮点数,还有一些特殊值(-Infinity
、+Infinity
、NaN
)String
:一串表示文本值的字符序列Symbol
:一种实例是唯一且不可改变的数据类型
对象类型
Object
:自己分一类丝毫不过分,除了常用的Object,Array、Function等都属于特殊的对象
二、原始类型和对象类型的区别
1.原始类型
在JavaScript中,每一个变量都需要一个内存空间来存储。
内存空间被分为两种:堆内存和栈内存。
JavaScript中的原始类型的值被直接存储在栈中,在变量定义时,栈就为其分配好了内存空间。
栈内存特点:
- 存储的值大小固定
- 空间较小
- 可以直接操作其保存的变量,运行效率高
- 由系统自动分配存储空间
原始类型的特点就是不可变性,即值本身是不可被改变的。
var str = 'JingYu';
str.slice(1);
str.substr(1);
str.trim(1);
str.toLowerCase(1);
str[0] = 1;
console.log(str); // JingYu
有一个特殊情况:
str += '6'
console.log(str); // JingYu6
这个情况是不满足不可变性了吗?不是的,在上面的代码中,我们执行了str += ‘6’的操作,实际上是在栈中又开辟了一块内存空间用于存储’JingYu6’,然后将变量str指向这块空间,所以这并不违背不可变性的特点。
2.引用类型
JavaScript中的引用类型(对象类型)的值实际被直接存储在堆内存中,在栈内存中只存储了一个固定长度的地址,这个地址指向堆内存中的值。
堆内存特点:
- 存储的值大小不定,可动态调整
- 空间较大,运行效率低
- 无法直接操作其内部存储,使用引用地址读取
- 通过代码进行分配空间
引用类型就不具有不可变性的特点了,我们可以轻易改变它,尤其是数组有很多函数可以改变:
pop()
删除数组最后一个元素,如果数组为空,则不改变数组,返回undefined,改变原数组,返回被删除的元素push()
向数组末尾添加一个或多个元素,改变原数组,返回新数组的长度shift()
把数组的第一个元素删除,若空数组,不进行任何操作,返回undefined,改变原数组,返回第一个元素的值unshift()
向数组的开头添加一个或多个元素,改变原数组,返回新数组的长度reverse()
颠倒数组中元素的顺序,改变原数组,返回该数组sort()
对数组元素进行排序,改变原数组,返回该数组splice()
从数组中添加/删除项目,改变原数组,返回被删除的元素
3.复制
当我们把一个变量复制到另一个变量的时候,原始类型和引用类型的表现也是不同的。
原始类型:
var name = 'JingYu';
var name2 = name;
name2 = 'JINGYU';
console.log(name); // JingYu;
通过输出结果可以看出,我们修改name2的结果的时候,对name没有任何的影响。那是因为我们将变量name
复制给name2
的时候是在栈内存空间中创建了一块新的内存空间,这块内存空间存储的是变量name2
的值,值与变量name
是相同的但是内存空间地址是完全不同的,所以修改name2
之后name
的值不变。
引用类型:
var obj = {name:'JingYu'};
var obj2 = obj;
obj2.name = 'JINGYU';
console.log(obj.name); // JINGYU
你会惊奇的发现,显示的结果和原始类型是不同的。这又是为什么呢?
那是因为当我们复制引用类型的变量时,实际上复制的是栈中存储的地址,所以复制出来的obj2实际上和obj指向的堆中同一个对象。因此,我们改变其中任何一个变量的值,另一个变量都会受到影响,这就是为什么会有深拷贝和浅拷贝的原因。
4.比较
先看一下代码,猜猜运行结果…
var name = 'JingYu';
var name2 = 'JingYu';
console.log(name === name2); // true
var obj = {name:'JingYu'};
var obj2 = {name:'JingYu'};
console.log(obj === obj2); // false
对于原始类型,比较时会直接比较它们的值,如果值相等,即返回true。
对于引用类型,比较时会比较它们的引用地址,虽然两个变量在堆中存储的对象具有的属性值都是相等的,但是它们被存储在了不同的存储空间,因此比较值为false。
5.值传递
首先给出结论:ECMAScript中所有的函数的参数都是按值传递的。
ECMAScript中是没有引用传递的。
let name = 'JingYu';
function changeValue(name){name = 'JINGYU';
}
changeValue(name);
console.log(name);
很明显,上面的执行结果是’JingYu’,即函数参数仅仅是被传入变量复制给了的一个局部变量,改变这个局部变量不会对外部变量产生影响。
let obj = {name:'JingYu'};
function changeValue(obj){obj.name = 'JINGYU';
}
changeValue(obj);
console.log(obj.name); //JINGYU
当函数参数是引用类型时,我们同样将参数复制了一个副本到局部变量,只不过复制的这个副本是指向堆内存中的地址而已,我们在函数内部对对象的属性进行操作,实际上和外部变量指向堆内存中的值相同,但是这并不代表着引用传递。
let obj = {};
function changeValue(obj){obj.name = 'JingYu';obj = {name:'JINGYU'};
}
changeValue(obj);
console.log(obj.name); // JingYu
obj = {name:'JINGYU'};
这个只是函数内部的局部对象。
三、浅拷贝
基础知识前面我们已经介绍过了。现在进入我们的正题。
概念
浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝。
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址
即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址
实现方法
简单的浅拷贝
function shallowClone(obj) {const newObj = {};for(let prop in obj) {if(obj.hasOwnProperty(prop)){newObj[prop] = obj[prop];}}return newObj;
}
在JavaScript中,存在浅拷贝现象的还有三种:
Object.assign
var obj = {age: 18,nature: ['smart', 'good'],names: {name1: 'fx',name2: 'xka'},love: function () {console.log('fx is a great girl')}
}
var newObj = Object.assign({}, fxObj);
Array.prototype.slice()
, Array.prototype.concat()
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
使用拓展运算符实现的复制
const fxArr = ["One", "Two", "Three"]
const fxArrs = [...fxArr]
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
四、深拷贝
概念
深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式:
_.cloneDeep()
const _ = require('lodash');
const obj1 = {a: 1,b: { f: { g: 1 } },c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
jQuery.extend()
const $ = require('jquery');
const obj1 = {a: 1,b: { f: { g: 1 } },c: [1, 2, 3]
};
const obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
JSON.stringify()
const obj2=JSON.parse(JSON.stringify(obj1));
但是这种方式存在弊端,会忽略undefined、symbol和函数
const obj = {name: 'A',name1: undefined,name3: function() {},name4: Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}
手写循环递归
function deepClone(obj, hash = new WeakMap()) {if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作if (obj instanceof Date) return new Date(obj);if (obj instanceof RegExp) return new RegExp(obj);// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝if (typeof obj !== "object") return obj;// 是对象的话就要进行深拷贝if (hash.get(obj)) return hash.get(obj);let cloneObj = new obj.constructor();// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身hash.set(obj, cloneObj);for (let key in obj) {if (obj.hasOwnProperty(key)) {// 实现一个递归拷贝cloneObj[key] = deepClone(obj[key], hash);}}return cloneObj;
}
五、浅拷贝、深拷贝和赋值的区别
下面首先借助两张图,可以更加清晰看到浅拷贝与深拷贝的区别
从上图发现,浅拷贝和深拷贝都创建出一个新的对象,但在复制对象属性的时候,行为就不一样
浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象
// 浅拷贝
const obj1 = {name : 'init',arr : [1,[2,3],4],
};
const obj3=shallowClone(obj1) // 一个浅拷贝方法
obj3.name = "update";
obj3.arr[1] = [5,6,7] ; // 新旧对象还是共享同一块内存console.log('obj1',obj1) // obj1 { name: 'init', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj3',obj3) // obj3 { name: 'update', arr: [ 1, [ 5, 6, 7 ], 4 ] }
但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
// 深拷贝
const obj1 = {name : 'init',arr : [1,[2,3],4],
};
const obj4=deepClone(obj1) // 一个深拷贝方法
obj4.name = "update";
obj4.arr[1] = [5,6,7] ; // 新对象跟原对象不共享内存console.log('obj1',obj1) // obj1 { name: 'init', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4',obj4) // obj4 { name: 'update', arr: [ 1, [ 5, 6, 7 ], 4 ] }
浅拷贝和赋值
赋值
var obj1={name:'张三',age:18,class:['一班']}var obj2=obj1//进行了赋值的操作obj2.name='李四'obj2.class[0]='二班'console.log(obj1)console.log(obj2)
从例子的可以看出赋值后的对象obj2改变,原对象obj1的值也改变.这是因为赋值后的对象obj2赋值的是原对象obj1的栈内存地址,他们指向的是同一个堆内存数据,所以对赋值后的对象obj2对数据进行操作会改变公共的堆内存中的数据,所以原对象的值也改变了。
浅拷贝
var obj1={name:'张三',age:18,class:['一班']}function qianCopy(obj){var obj2={}for(var attr in obj){//循环对象的所有属性值if(obj.hasOwnProperty(attr)){obj2[attr]=obj1[attr]}}return obj2}var obj3=qianCopy(obj1)obj3.name='李四'obj3.age = 20obj3.class[0]='二·班'console.log(obj1)console.log(obj3)
从结果可以看出obj3改变了基本类型的值name,并没有使原对象obj1的name改变,obj3改变了引用类型的值,导致原对象的值也改变了
六、小结
赋值是完全复制,将一个对象赋值给另一个对象时,只是将一个对象在栈中的内存地址复制给另外一个对象,如果改变其中一个对象的属性值时另外一个对象的属性值也会跟着改变
浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,复制的是对象的内存地址,两个对象指向同一个地址
深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
相关文章:

【吃透Js】深入学习浅拷贝和深拷贝
一、JavaScript数据类型原始类型对象类型二、原始类型和对象类型的区别1.原始类型2.引用类型3.复制4.比较5.值传递三、浅拷贝概念实现方法四、深拷贝概念五、浅拷贝、深拷贝和赋值的区别浅拷贝和赋值六、小结想要真正搞明白深浅拷贝,你必须要熟练掌握赋值、对象在内…...

AUTOSAR为啥要开发新的社区商业模式?
总目录链接>> AutoSAR入门和实战系列总目录 文章目录1 自适应平台架构中的集群更新1.1 ara::diag 服务(诊断)更新1.2 信号到服务映射和自动驾驶接口让我们讨论一下信号到服务映射服务:Automated Driving Interface:2 车载应用商店概念本文介绍Re…...
数据结构和算法面试常见题必考以及前端面试题
1.数据结构和算法 1.1 反转单向链表 public class Node {public int value;public Node next; }public Node reverseList(Node head) {Node pre null;Node next null;while (head ! null) {next head.next;head.next pre;pre head;head head.next}return pre; }1.2 在顺…...

一文解决Python所有报错
前言 Python是一种强大的编程语言,但是它也有一些报错,这些报错可能会让你感到困惑。本文将介绍如何解决Python中的常见报错。 首先,让我们来看看Python中最常见的报错:SyntaxError。这种报错表明你的代码中有语法错误,…...

LeetCode 1237. Find Positive Integer Solution for a Given Equation【双指针,二分,交互】
本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…...

【C语言】结构体进阶
一、结构体 1. 结构体的声明 (1) 结构的基础知识 结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。(2)结构的声明 struct tag {member-list; }variable-list;例如描述一个学生&#x…...
全志T3+FPGA国产核心板——Pango Design Suite的FPGA程序加载固化
本文主要基于紫光同创Pango Design Suite(PDS)开发软件,演示FPGA程序的加载、固化,以及程序编译等方法。适用的开发环境为Windows 7/10 64bit。 测试板卡为全志T3+Logos FPGA核心板,它是一款基于全志科技T3四核ARM Cortex-A7处理器 + 紫光同创Logos PGL25G/PGL50G FPGA设计…...

深度学习之 imgaug (图像增强)学习笔记
深度学习之 imgaug (图像增强)前言1\. 安装和卸载2\. 示例2.1 基本使用2.2 包含常用的变换示例3 Augmenters常用函数3.1 iaa.Sequential()3.2 iaa.someOf()3.3 iaa.OneOf()3.4 iaa.Sometimes()3.5 iaa.WithColorspace()3.6 iaa.WithChannels()3.7 iaa.No…...

mysql字符串等值查询中条件字段值末尾有空格也能查到数据问题
一、事故还原 我们仍然使用学生信息表,但是我们只需要保留两个字段即可: CREATE TABLE student_info (id int(11) NOT NULL AUTO_INCREMENT COMMENT 学号,name varchar(20) CHARACTER SET utf8 DEFAULT NULL COMMENT 姓名, PRIMARY KEY (id) ) ENGINEIn…...
一个关于事件溯源Event Sourcing的小荔枝,Golang实现
最后更新于2023年3月1日 10:23:13 参考的这个文章:https://martinfowler.com/eaaDev/EventSourcing.html 用C sharp实现的,我改写成Golang了 最简单的例子 func main() {eProc : NewEventProcessor()//refact : Cargo{Name: "Refactoring"}…...

Vue3 组合式函数,实现minxins
截至目前,组合式函数应该是在VUE 3应用程序中组织业务逻辑最佳的方法。它让我们可以把一些小块的通用逻辑进行抽离、复用,使我们的代码更易于编写、阅读和维护。 一. 什么是“组合式函数”? 根据官方文档说明,在 Vue 应用的概念中…...

什么是钉钉消息推送?
我是3y,一年CRUD经验用十年的markdown程序员👨🏻💻常年被誉为职业八股文选手 在前阵子我就已经接入了钉钉的群机器人和工作消息推送,一直没写文章同步到给大家。 像这种接入渠道的工作,虽然我没接入过&…...

利用 NVIDIATAO 和 WeightBias 加速AI开发
利用 NVIDIATAO 和 Weight&Bias 加速AI开发 利用图像分类、对象检测、自动语音识别 (ASR) 和其他形式的 AI 可以推动公司和商业部门内部的大规模转型。 然而,从头开始构建人工智能和深度学习模型是一项艰巨的任务。 构建这些模型的一个共同先决条件是拥有大量高…...
token - 令牌
文章目录token - 令牌学前须知:1,base64 防君子不防小人2,SHA-256 安全散列算法的一种(hash)3,HMAC-SHA2564,RSA256 非对称加密2.1 JWT - json-web-token1,三大组成2,jwt…...

应用模型开发指南上新介绍
Module、HAP、Ability、AbilitySta-ge、Context……您是否曾经被这些搞不懂又绕不开的知识点困扰? 现在,全新的《应用程序包基础知识》及《应用模型开发指南》为您答疑解惑! 这里有您关注的概念解析、原理机制阐述,也有丰富的…...

Dbeaver连接Hive数据库操作指导
背景:由于工作需要,当前分析研究的数据基于Hadoop的Hive数据库中,且Hadoop服务端无权限进行操作且使用安全模式,在研究了Dbeaver、Squirrel和Hue三种连接Hive的工具,在无法绕开useKey认证的情况下,只能使用…...

【RabbitMQ笔记09】消息队列RabbitMQ之常见方法的使用
这篇文章,主要介绍消息队列RabbitMQ之常见方法的使用。 目录 一、消息队列常见方法 1.1、连接工厂ConnectionFactory 1.2、连接Connection 1.3、通道Channel 1.4、交换机相关方法 (1)exchangeDeclare()声明交换机 1.5、队列相关方法 …...

Linux字符设备驱动模型之设备号
从上文中可知,在Linux用户空间中,如若需要操作硬件设备,均通过/dev目录下的设备文件节点进行操作,基本上每一种设备都会存在一个或者多个的设备节点。 并且在Linux内核中,其表示字符设备的结构成员也提供了相应的设备号…...

C++多态原理
请看下面的程序,该程序演示了多态类对象存储空间的大小。 #include <iostream> using namespace std; class A {public:int i;virtual void func() {}virtual void func2() {} }; class B : public A {int j;void func() {} }; int main() {cout << si…...

PMP认证与NPDP认证哪个含金量高?
两个证涉及的领域不一样的,一个是项目管理,对应的是项目经理;一个是产品管理,对应的是产品经理。含金量不能相比,但在各自的领域的含金量是很高的,至少专业程度或者知名度是最高的。 我来分别说一下PMP认证…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...

Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...

R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...

莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...

代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...