js对象的直接赋值、浅拷贝与深拷贝
最近Vue项目中写到一个业务,就是需要把对话框的表单中的数据,每次点击提交之后,就存进一个el-table表格中,待多次需要的表单数据都提交进表格之后,再将这个表格提交,实现多个表单数据的同时提交,期间还可以用表格进行预览、修改等其他操作。将每个表单数据存进表格的代码大致代码如下:
let object=this.ruleForm;
this.tableData.push(object);
其中,对话框中的表单使用了el-form,this.ruleForm是vue实例中的一个对象,而this.tableData是vue实例中的一个数组对象。直接将this.ruleForm赋值给一个变量object,然后每次再push进this.tableData里,这样看上去逻辑似乎也没啥毛病,但是,这样就会产生一个神奇的现象:每次填写表单中的数据的时候,表格中的每一行数据都会随着你表单的填写的改变而改变。
这里就是出现了题目所谈到的问题,涉及到了js对象的直接赋值、浅拷贝与深拷贝。
直接赋值
把一个对象a赋值给一个对象b相当于把一个对象b的地址指向对象a的地址,所以,他们实际上是同一个对象。由于这个项目是Vue,这次的问题就出现在了直接赋值上,Vue的响应式会让你更直观的知道他们的实质。以图1直接赋值的例子,person对象中有两个属性,一个是name,一个是对象属性ageAndSex;为什么要弄一个对象属性,这个会涉及到后面的浅拷贝和深拷贝问题,这也是他们之间的区别。由于内存地址我们很难监测到,但是我们可以通过严格相等运算符"==="来检测二者是否指向同一个地址。

图1 如果二者都是对象,严格相等运算符则会去检查它们是否指向相同的内存地址。
以刚才的例子为例,如图2所示。刚开始的时候给personCopy的name属性赋值小刚,发现,person也发生了改变。给personCopy的对象属性ageAndSex的age属性赋值17,person也发生了改变。即:直接赋值,修改赋值后的对象b的非对象属性,也会影响原对象a的非对象属性;修改赋值后的对象b的对象属性,也会影响原对象a的对象属性。

图2 直接赋值
浅拷贝
浅拷贝只会赋值制对象的非对象属性,不会指向同一个地址。ES6中有个浅拷贝的方法Object.assign(target, ...sources)。以之前直接赋值的对象为例,如图3所示。

图3 浅拷贝,赋值的对象与被复制的对象不会指向同一个地址
修改赋值后的对象b的非对象属性,不会影响原对象a的非对象属性;修改赋值后的对象b的对象属性,却会影响原对象a的对象属性,如图4所示。

图4 浅拷贝
es6中还有一个扩展运算符"..."也可以实现浅拷贝,还是以之前的对象为例,可以写成这种形式:var personCopy= { ...person };如图5所示。

图5 扩展运算符实现浅拷贝(赋值"小刚"等的操作与之前的结果完全相同,就不全贴出来了)
考虑到es6的支持程度,如果你的项目不支持es6,但是又想实现浅拷贝的话,也可以尝试js原生的concat方法。但由于concat只能操作数组,所以需要先将person封装为一个对象数组,写成这种形式:
var person=[{name:"小明",ageAndSex:{age:16,sex:"男"}}];
var personCopy=[].concat(person);
如图6所示,到时想得到person对象的时候var personCopyObjet=pesronCopy[0]即可。

图6 concat方法实现浅拷贝
深拷贝
深拷贝会另外拷贝一份一个一模一样的对象,但是不同的是会从堆内存中开辟一个新的区域存放新对象,新对象跟原对象不再共享内存,修改赋值后的对象b不会改到原对象a。即深拷贝,修改赋值后的对象b的非对象属性,不会影响原对象a的非对象属性;修改赋值后的对象b的对象属性,也不会影响原对象a的对象属性。而且,二者不指向同一个对象。
很明显,深拷贝比较符合我这次的业务需求。深拷贝,比较笨一点的办法就是将自己需要的数据自己封装起来。
但是,这样明显会使代码很臃肿,而且,这还是在需要的数据只有4条的情况下,如果这个object需要封装十几条非对象属性的情况下,明显结构不复杂的情况下,这种代码需要改进。
有一种非常简单的方法就是序列化成为一个JSON字符串,将对象的内容转换成字符串的形式,再用JSON.parse()反序列化将JSON字符串变成一个新的对象,这样原对象就与复制后的新对象没了必然的关系。以前文提到的personCopy和person为例,写法如下:var personCopy=JSON.parse(JSON.stringify(person));如图7所示。

图7 深拷贝
但是由于用到了JSON.stringify(),这也会导致一系列的问题,因为要严格遵守JSON序列化规则:原对象中如果含有Date对象,JSON.stringify()会将其变为字符串,之后并不会将其还原为日期对象。或是含有RegExp对象,JSON.stringify()会将其变为空对象,属性中含有NaN、Infinity和-Infinity,则序列化的结果会变成null,如果属性中有函数,undefined,symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失,因为不支持。
所以,这个时候笨的办法也是有好处的,就是面对一些特殊的类型,或是对象属性复杂的情况下,因为自己对程序的需求比较了解,就可以按照自己的需要进行封装。不管黑猫白猫,能抓到老鼠的就是好猫。
js对象的直接赋值、浅拷贝与深拷贝的更多相关文章
- 对Python中列表和数组的赋值,浅拷贝和深拷贝的实例讲解
引用:https://www.jb51.net/article/142775.htm 列表赋值: 1 2 3 4 5 6 7 >>> a = [1, 2, 3] >>&g ...
- JS中的引用、浅拷贝和深拷贝
js的深拷贝浅拷贝是很常遇到的问题,一直模模糊糊有点说不过去,所以这次好好总结一下. 1.js的引用 JS分为基础类型和引用类型两种数据类型: 基础类型:number.string.boolean.n ...
- 编写高质量代码改善C#程序的157个建议[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议13.为类型输出格式化字符串 建议14.正确实现浅拷贝和深 ...
- 编写高质量代码改善C#程序的157个建议——建议14: 正确实现浅拷贝和深拷贝
建议14: 正确实现浅拷贝和深拷贝 为对象创建副本的技术称为拷贝(也叫克隆).我们将拷贝分为浅拷贝和深拷贝. 浅拷贝 将对象中的所有字段复制到新的对象(副本)中.其中,值类型字段的值被复制到副本中后, ...
- C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议13.为类型输出格式化字符串 建议14.正确实现浅拷贝和深 ...
- C#浅拷贝与深拷贝测试
1.浅拷贝与深拷贝 浅拷贝:只复制对象的基本类型,对象类型,仍属于原来的引用. 深拷贝:不紧复制对象的基本类,同时也复制原对象中的对象.就是说完全是新对象产生的. 2.浅拷贝与深拷贝的区别 ...
- python 进阶篇 浅拷贝与深拷贝
阐述引用.浅拷贝和深拷贝前,首先需要要了解 Python 的世界里,一切皆对象,每个对象各包含一个 idendity.type 和 value. 引用(Reference) >>> ...
- JS 对象的深拷贝和浅拷贝
转载于原文:https://www.cnblogs.com/dabingqi/p/8502932.html 这篇文章是转载于上面的链接地址,觉得写的非常好,所以收藏了,感谢原创作者的分享. 浅拷贝和深 ...
- js对象的浅拷贝与深拷贝
浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用(堆和栈的关系,原始(基本)类型Undefined,Null,Boolean,Number和String是存入堆,直接引用,ob ...
随机推荐
- 如何判断float值有效
// 一个浮点数是否有效,首先要看其是否是一个数字(_isnan为0),其次还要看其是否超出了表示范围(_finite为0) // 注意_finite是有限的意思 #include <float ...
- 汇编语言——物理地址=段地址x16+偏移地址,检测点2.2
一.为什么 物理地址=段地址x16+偏移地址? 刚开始学时,我都笨到不明白为什么是2的N次方,咱把物理地址就当数字,计算机中数字是由很多位0或1自由组合的, 而每一位上要么是0要么是1,只有这两种情况 ...
- 《JavaScript设计模式与开发实践》-- 策略模式
详情个人博客:https://shengchangwei.github.io/js-shejimoshi-celue/ 策略模式 1.定义 策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们 ...
- 使用msfvenom生成木马
msfvenom Options: -p, --payload < payload> 指定需要使用的payload(攻击荷载).如果需要使用自定义的payload,请使用& #03 ...
- 使用memset初始化int数组
memset()是一个来自于string库的函数,正规用法是初始化char类型的数组.因为char类型只占1个字节,memset按字节赋值后,会将char类型数组的所有元素变为你指定的值.但是4字节的 ...
- Android 常见内存泄露 & 解决方案
前言 内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃 (OOM) 等严重后果. 那什么情况下不能被 ...
- ITester软件测试小栈,快来点击领取你的能量值!
日供一卒,功不唐捐,这不是一个非正常更新的ITester软件测试小栈,不定期分享软件测试相关,包括功能.接口.自动化.性能.专项.测试开发,简历指点,面试助攻,群而不党,和而不同,如趋同,且同行.
- Asp.net Core 系列之--3.领域、仓储、服务简单实现
ChuanGoing 2019-11-11 距离上篇近两个月时间,一方面时因为其他事情耽搁,另一方面也是之前准备不足,关于领域驱动有几个地方没有想通透,也就没有继续码字.目前网络包括园子里大多领域驱 ...
- nmap学习笔记-扫描格式
习惯性的前言: 之前曾经零零星星的学习过一段时间的nmap,但是因为用的少,后续有慢慢的放下了,这次正好借着工作上的机会重新学习一下nmap,并记录在此. nmap端口状态: open:应用程序在该端 ...
- raw文件转mha文件
raw格式 在体数据(volume)中,经常会遇到raw文件,raw文件就是其实就是所有体素组成的文件,raw文件必须还有一些描信息才能用(因为得知道数据的size,type,spacing等),就像 ...