前些天写Vue项目遇见一个很有意思问题:将一个数据(类型是对象)赋值给一个变量,当我去改变这个变量的时候,给它赋值的数据也跟着变化了,当我去改变数据时,变量也跟着变了,这让我想起了js中的浅复制和深复制,为验证,将对象(引用类型)改成字符窜(基本数据类型),果然,这个问题就不见了。
在js中有两种数据类型
(1) 基本数据类型:number、string、boolean、null、undefined、symbol(ES6)
(2) 引用数据类型:object、function(函数实际也是对象)
为何会分为这两种数据类型呢?
个人见解:
在一段程序中必定会有这样的场景:
(1) 当一个方法执行完后,不再引用的变量会被销毁,被引用的变量不会被销毁,不会造成资源浪费和多余的性能消耗;
(2) 定义一个变量时,这个变量会被自动分到对应内存中(栈内存和堆内存),提高变量查询的速度;例如,定义一个未知大小的变量(如:对一个对象的增加删除),放在较小内存的栈中,栈大小是有默认值的,如果申请的临时变量太大的话就会超过栈大小,造成栈溢出,很明显会影响性能和查找速度。反之,如果一个固定大小的变量放到堆内存中,实际堆内存是可以申请大小的(相当于一个自适应的网页),只要不超出内存大小;很明显会造成资源利用不合理。

浅复制:
在这里插入图片描述
将一个对象的变量赋值给另一个变量,修改其中一个变量时,另一个的值也会跟着变化,这就是浅复制,输出结果如下:
在这里插入图片描述
当将对象修改为字符串时,赋值给一个变量,修改其中一个值时,另一个并不会变化,如下:
在这里插入图片描述
在这里插入图片描述
为什么???
原来在Js中基本数据类型是直接按值存储在栈内存中的,而引用类型的值是存储在堆内存中的,Js代码都是自上而下的在栈内存中执行的(有一些资料显示,js并没有从严格意义上去区分栈和堆,在一些场景下也是有所区分的,例如:浅复制和深复制),那堆内存中的数据就没有用了吗???
可能是为了解决这个问题,在栈内存中给堆内存开辟一个专门用来放置它的地址,告诉js引擎,我在外面游荡,但是你可以通过这个地址找到我。
所以,这就可以想到,当我们直接将一个引用数据类型赋值给一个变量的时候,实际上只是在栈内存中执行复制了一下这个对象的地址,并不是这个对象实际的值,同一个地址指向的当然就是同一个值。
解决的方法:
既然知道了问题所在就要去干掉它,首先定一个目标:变量怎样获取到对象实际的值???

深复制:
在这里插入图片描述
通过上面封装的方法在执行,如下:
在这里插入图片描述
在这里插入图片描述
问题解决了,实际上这是因为obj通过遍历将值都赋值给了变量obj1,obj1也是一个完整实体存在了,只是与obj相似而已,当然obj1在栈内存中也有了一个自己专属的地址,所以obj和obj1实际就不存在任何关联了。
深复制的方法有很多,介绍一种最常用的:
JSON.parse(JSON.stringify(obj))
个人理解:这个实际就是利用JSON.stringify(obj)将对象的内容转换成字符串,那么在栈内存中就会给他一个空间存储,之后这个字符串想去外面的世界看看有多精彩,通过JSON.parse还原回原来的对象,带着家一起出走到了堆内存中,在栈内存中留下了联系方式(地址),从而实现了深复制。
注意:JSON.parse(JSON.stringify(obj))不能复制函数类型,obj也是要可以枚举才行,在IE7以下浏览器会报错

对于js基本数据类型的赋值谈不上是深复制,因为每每声明一个变量时,栈内存中就会给其一个固定空间,如下面的a和b,实际他们两个都在各自的空间,空间里面都放着实际值,互不干扰。
Var a=1;
Var b=a;

番外:
Null的数据类型实际是object类型,为何会在基本数据类型里面呢???
查看资料很多都说是一个将错就错的bug
实际想一下,很多关于提高性能的书里面都有提到一个“对象不用时就obj=null“,这是因为浏览器有一个垃圾回收机制,当检测到这原有的堆内存没有被占用了就会被销毁,null就相当于一个对象的空地址,值就是固定的,占用空间是固定的,这可能就是将错就错的原因,没有用的全局对象不手动销毁,浏览器在不能检测这个变量何时不再使用,就不会销毁,会造成内存泄漏。

Logo

前往低代码交流专区

更多推荐