通过堆栈分析深拷贝、浅拷贝、赋值的差异
前言
数据类型分为:
基本数据类型String、Number、Boolean、Null、Undefined、Symbol
对象数据类型Object、Array
基本数据类型的特点:直接存储在栈(stack)中的数据
引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里

引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
深拷贝与浅拷贝
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
赋值是深拷贝还是浅拷贝?
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
直接赋值:
var origin = {db:{id:'001'},num:33,read:[1,2,3,4]}
var data = origin;
origin.num= 10;

origin.read = [1,2];
origin.db.id = '002;

origin.db = {text:'aaa'}

那么浅拷贝跟赋值会是一样的吗?
浅拷贝:
假设我们有个浅拷贝的方法,叫shallowCopy。具体的浅拷贝的方法请看本文的附录-浅拷贝方法
var origin = {db:{id:'001'},num:33,read:[1,2,3,4]}
var data = shallowCopy(origin);
origin.num = 10;

origin.read[0] = 10;

origin.db.id = '003'

于是,我们可以说:赋值的话,旧对象的改变一定会引起新对象的改变(无论在一层或N层)

为什么要份第一层还是第N层,我们来看这个梨子:
const data = {arr:[1,2,3],inner:{arr:[3,4,5]}}
const data2 = shallowCopy(data)
data.arr = [0,0,0];

data.inner.arr = [0,0,0]

可以看到浅拷贝后第一层引用对象不关联。具体见附录-浅拷贝面试题
深拷贝方法

可以看到,深拷贝后的结果与源数据是两个完全独立的数据。
深拷贝方法实现:
一、 通过JSON.stringify()
var a = {data:{name:'xxx'}}
var b = JSON.parse(JSON.stringify(a))
b; // {data:{name:'xxx'}}
a.data.name = 'abc';
b // {data:{name:'xxx'}}
JSON.stringify()进行深拷贝有弊端:
var obj = {a:function(){}, b: undefined, c: null, d: Symbol('s'), }
var cloneObj = JSON.parse(JSON.stringify(obj ))
cloneObj // {c:null}
会忽略value为function, undefind, symbol, 并且在序列化BigInt时会抛出语法错误:TypeError: Do not know how to serialize a BigInt
更多弊端请看本文附录-JSON.parse(JSON.stringfy(对象))弊端
二、函数库lodash
该函数库也有提供_.cloneDeep用来做 Deep Copy

三、手写递归
递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝

附录-浅拷贝方法
一、手写浅拷贝
// 数组 对象都可以用
const shallowClone = (obj) => {const dst = {};for (let prop in obj) {if (arr.hasOwnProperty(prop)) {dst[prop] = obj[prop];}}return dst;
}
for...in:遍历 Object 对象 obj,将可枚举值列举出来。
hasOwnProperty():检查该枚举值是否属于该对象 obj,如果是继承过来的就去掉,如果是自身的则进行拷贝。
二、Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
var obj = {data:{name:'xxx'}}
var newCloneObj = Object.assign({},obj)
obj.data.name ='ddd'
newCloneObj.data.name// ddd
三、ES6的拓展运算符
var obj = {data:{name:'xxx'}}
var newCloneObj = {...obj}
obj.data.name ='ddd'
newCloneObj.data.name // ddd
四、Object.create()
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
Object.create(proto,[propertiesObject])接收两个参数一个是新创建对象的__proto__, 一个属性列表
let aa = {a: undefined, func: function(){console.log(1)}, b:2, c: {x: 'xxx', xx: undefined},
}
let bb = Object.create(aa, Object.getOwnPropertyDescriptors(aa))
五、Array.prototype.concat()
concat() 是数组的一个内置方法,用户合并两个或者多个数组。
这个方法不会改变现有数组,而是返回一个新数组。
const arr1 = [1,{username: 'jsliang',},
];let arr2 = arr1.concat();
arr2[0] = 2;
arr2[1].username = 'LiangJunrong';
console.log(arr1);
// [ 1, { username: 'LiangJunrong' } ]
console.log(arr2);
// [ 2, { username: 'LiangJunrong' } ]
六、Array.prototype.slice()
slice() 也是数组的一个内置方法,该方法会返回一个新的对象。
slice() 不会改变原数组。
const arr1 = [1,{username: 'jsliang',},
];let arr2 = arr1.slice();
arr2[0] = 2;
arr2[1].username = 'LiangJunrong';
console.log(arr1);
// [ 1, { username: 'LiangJunrong' } ]
console.log(arr2);
// [ 2, { username: 'LiangJunrong' } ]
附录-浅拷贝面试题
我们先来看看
const data = {arr:[1,2,3],inner:{arr:[3,4,5]}}
const data2 = shallowCopy(data)
data.arr[0] = 3

data.arr = [0,0,0]

data.arr[0] = 3

继上面的内容,我们先解析一下:
不论变量是存在栈内,还是存在堆里(反正都是在内存里),其结构和存值方式是差不多的,都有如下的结构:

let foo = 1;
let bar = 2;


我们新建data2:

当我们第一次修改源数据data.arr[0]:
相对于我们去修改堆空间里对应内存地址的值。而不是开劈新的内存空间进行存值。
(堆空间里基本数据类型的替换将沿用原来的内存地址,如果引用类型的替换则将开辟新的内存地址,等待回收老的内存地址【不一定会回收,因为可能被其他引用】)

此时并不会改变data和data2里arr的引用地址。所以data和data2同步关联。
第二次修改源数据data.arr = [0,0,0]:

可以看到的是堆里新开辟了一个空间放引用对象arr的内容。同时解绑data.arr的引用地址,把新空间的地址绑定到data.arr上,而data2.arr由于还是指向0x0206的内存空间,所以旧的内存空间没有被收回。data2.arr未发生改变
再回归到问题本身:
为什么data.inner.arr = [0,0,0]会同时改变data和data2里面的值?
你可以看到,data和data2的inner,即第二层,都是指向同一个内存空间,

当我们去改变inner对象内容里面的赋值的时候,data和data2的inner指向地址不变,只不过里面的arr我们会重新读值。使得两者改动时发生关联。
所以我们说,无论浅拷贝通过什么方法实现,拷贝结果的第一层会是深拷贝(两个内存空间)
附录-JSON.parse(JSON.stringfy(对象))弊端
如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。
如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象。
如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失。
如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。
JSON.stringify()只能序列化对象的可枚举的自有属性。如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor。
如果对象中存在循环引用的情况也无法正确实现深拷贝。
function Person (name) {this.name = 20
}const lili = new Person('lili')let a = {data0: '1',date1: [new Date('2020-03-01'), new Date('2020-03-05')],data2: new RegExp('\\w+'),data3: new Error('1'),data4: undefined,data5: function () {console.log(1)},data6: NaN,data7: lili
}let b = JSON.parse(JSON.stringify(a))

相关文章:

通过堆栈分析深拷贝、浅拷贝、赋值的差异
前言数据类型分为:基本数据类型String、Number、Boolean、Null、Undefined、Symbol对象数据类型Object、Array基本数据类型的特点:直接存储在栈(stack)中的数据引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内…...

网络割接概述
网络割接概述割接背景企业网络的变化割接概述割接难点割接的操作流程情景模拟及解决方案常见的割接场景割接背景 随着企业业务的不断发展,企业网络为了适应业务的需求不断的改造和优化。无论是硬件的扩容、软件的升级、配置的变更,凡是影响现网运行业务…...
开放开源开先河(下)
目录 1.唯一性定义品牌 2.打造爆款塑造品牌 3.生态系统传播品牌 打造爆款塑造品牌 目前全球100多个开源基金会大部分都在美国,已成功孵化了800多个项目。而开放原子开源基金会现有136家捐赠单位,2020年9月,百度将区块链项目超级链࿰…...

maven的学习
为啥要用maven 1、不用认为添加jar包所依赖的其他jar包 2、能在本地仓库只保留一份jar包,避免了多个工程使用相同jar包,需要重复导入的问题,减少冗余 3、能够规范添加jar包,在下载需要的jar包时有多种方法,但是不能保…...
从前端到后端全面解析文件上传
从前端到后端全面解析文件上传1.前端准备(vueelement-ui)2.后端准备(SpringBootminiomysql)2.1解决跨域2.2配置minio与mysql2.3controller层2.4service层1.前端准备(vueelement-ui) <!DOCTYPE html> <html lang"en"> <head><meta charset"…...

全网火爆,软件测试面试题大全,接口测试题+回答 (18k+的offer)
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 面试测试工程师的时…...

【iOS】—— 浅看block源码
block 文章目录block如何通过终端clang生成源码cpp文件block实质截获自动变量全局变量和静态变量的截获__block说明符iOS开发“强弱共舞”——weak和strong配套使用解决block循环引用问题如何通过终端clang生成源码cpp文件 之前在学习block中学习的比较浅,只看了oc…...

I.MX6ULL_Linux_系统篇(23) busybox文件系统构建
Linux“三巨头”已经完成了 2 个了,就剩最后一个 rootfs(根文件系统)了,本章我们就来学习一下根文件系统的组成以及如何构建根文件系统。这是 Linux 移植的最后一步,根文件系统构建好以后就意味着我们已经拥有了一个完整的、可以运行的最小系…...
shpjs将.zip文件转成geoJson
一、npm install shpjs二、import shp from shpjs三、async setLayerSource() {const geoJsonData await shp(dataUrl)}一直报错:是因为Buffer这个插件一直没找到Uncaught Error: nodebuffer is not supported by this browser解决办法npm install node-polyfill-w…...

eBay是不是一定要养号?是的
相信每个运营过eBay的用户遇到过这个棘手的问题,eBay个人账户的刊登数量是有限的,尤其是新账户只有5个sku,所以一开始的运营会比较艰难。想要快点走上正轨的话,就一定要去注重这个“养号”。eBay运营模式 1.拍卖 eBay最开始是一个…...

宝塔(二):升级JDK版本
目录 背景 一、下载JDK17 二、配置环境变量 三、配置新的JDK路径 背景 宝塔的软件商店只有JDK8,不满足我当前项目所需的JDK版本,因此想对JDK版本进行升级,升级为JDK17。 一、下载JDK17 先进入 /usr/lib/jvm 目录 点击终端,进…...

【LeetCode】螺旋矩阵 [M](数组)
54. 螺旋矩阵 - 力扣(LeetCode) 一、题目 给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。 示例 1: 输入:matrix [[1,2,3],[4,5,6],[7,8,9]] 输出:[1,2,3,…...

实验二:动态规划
1.双11的红包雨 问题描述 双11到了,据说这2天会下红包雨,每个红包有不同的价值,小k好开心,但有个规则,就只能接掉落在他身旁的10米范围内的红包(0-10这11个位置)。小k想尽可能的多抢红包&…...
华为机试 HJ27 查找兄弟单词
题目链接:https://www.nowcoder.com/practice/03ba8aeeef73400ca7a37a5f3370fe68?tpId37&tqId21250&rp1&ru/exam/oj/ta&qru/exam/oj/ta&sourceUrl%2Fexam%2Foj%2Fta%3Fdifficulty%3D3%26page%3D1%26pageSize%3D50%26search%3D%26tpId%3D37%26t…...

佩戴舒适的蓝牙耳机有哪些?佩戴舒适的蓝牙耳机推荐
音乐对许多人而言,都是一种抚慰生命的力量,特别是在上下班的时候,在熙熙攘攘的人流中,戴着耳机听一首动听的曲子,无疑会让人心情变得更加舒畅,要想获得出色的音乐体验,没有一副出色的耳机可不行…...

ESXI主机安装Zabbix 6.2
1:首先下载Zabbix Appliance 2:还需要下载VMware converter ,这个需要VMware的账号,或者从其他地方下载也可以。 3:vmdk格式的 image 可直接在 VMware Player、 Server 和 Workstation 产品中使用。要在 ESX,、ESXi 和 vSphere 中…...

【Linux 网络编程1】使用UDP/TCP编写套接字,多进程/多线程版本的TCP编写的套接字,将套接字封装
目录 1.学习网络编程前的一些基础知识 2.UDP(user datagram protocol)协议的特点 3.使用有UPD编写套接字 4.使用TCP编写套接字 4.2.TCP客服端 4.3.TCP服务器端 4.4.单进程版本(没有人会使用) 4.5.多进程版本 4.6.多线程版本 5.把套接字封装 1…...

滑动窗口求最大和最小
滑动窗口 要区分最小和最大滑窗,内层while循环的条件和更新结果的地方 核心: 关键的区别在于,最大滑窗是在迭代右移右边界的过程中更新结果,而最小滑窗是在迭代右移左边界的过程中更新结果。 最小滑窗 给定数组 nums࿰…...
跟ChatGPT学量化,菜鸟炒股也不怕
你是一个python资深量化开发工程师,请介绍量化交易的基本框架 量化交易的基本框架包括以下几个步骤: 数据获取:获取市场数据和公司财务数据等,可以使用API接口、爬虫等方式获取。数据清洗和预处理:对获取到的数据进行清…...

扬帆优配|2600亿新能源巨头狂飙!外资唱多中国:再涨15%
全国停摆的危机,正在迫临法国。 大停工正在将法国推向风险境地。法国政府估计,当地时间3月7日,将迸发全国大型停工游行。法国总工会宣告,到时将让全法国停摆。法国担任交通业务的部长级代表克莱蒙博讷正告称,7日将成为…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...