最近在写vue项目,vue项目存取数据较之传统html项目而言根据简洁方便,直接使用数据双向绑定就能实现功能。在这种情况下,对绑定的数据做初始赋值就是很常见的操作,简单类型数据,如字符串,直接使用字面量传值到是没什么问题。但是业务复杂的情况下,我们常常会用对象来进行数据绑定,而针对对象的赋值操作,就涉及到了一个概念,就是深拷贝和浅拷贝。
简单了解下概念,在javascript中,包括两种数据类型,一种是基本数据类型,如:String,Boolean,Number,Undefined,Null。另一种是引用数据类型,如:Object(Array,Date,RegExp,Function)。
两种数据类型的根本区别是什么呢?两者的保存位置不同,基本数据类型保存在栈内存中,引用数据类型保存在堆内存中,然后在栈内存中保存了一个对堆内存中实际对象的引用,即数据在堆内存中的地址,JS对引用数据类型的操作都是操作对象的引用而不是实际的对象,如果obj1拷贝了obj2,那么这两个引用数据类型就指向了同一个堆内存对象,具体操作是obj1将栈内存的引用地址复制了一份给obj2,因而它们共同指向了一个堆内存对象。
浅拷贝和深拷贝都只针对于引用数据类型,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存;但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
了解完了概念后,再来看看业务,初始化绑定数据。基于双向绑定的原因,我们会有多个场景会进行数据初始化操作,比如初次进入新增界面时,比如进入编辑页面时,为避免数据交叉显示,在赋值前,需要先进行数据初始化,清空之前显示的内容。所以,我们可以自定义一个对象,其属性都是默认值,比如0,null,空字符串等,而在每次数初始化时,将该对象的内容拷贝到绑定的数据中即可。这种拷贝一般都应该是深拷贝,不进行引用拷贝,否则修改了绑定的数据后,我们的默认对象的值也进行了修改,就无法达到默认对象初始化数据的功能了。
拷贝数据的方式其实有很多种,可以通过JSON.parse、JSON.stringify 将对象转换成字符串再转换成对象,这种操作就是深拷贝,也可以通过...扩展运算符来拷贝对象。json的方式拷贝对象是没有问题的,而扩展运算符的方法呢?
JavaScript的扩展运算符是用来做什么的呢?扩展运算符的功能类似于Object.assign() 方法,用于取出参数对象的所有可遍历属性,然后拷贝到当前对象中。写法如下,var aaa = {...bbb} 将bbb对象的全部属性拷贝到aaa对象中。
这种拷贝对象的方式是写法最简单的,并且看起来这种方式拷贝对象也是创建一个全新的对象,不是直接复制引用。如果你以为这种方式是深拷贝那就大错特错了。曾经我也是这么认为的,然而现实用了两个小时的时间,教我做人。
这是一个悲伤的故事,在一个炎炎夏日的下午,我在编写一个模块的前端页面。由于业务较为复杂,所以接口的传输数据是个一米高的对象,嵌套四五层。数据再复杂也做初始化操作,于是我像往常一样,使用了{...}进行对象赋值操作。然而神奇的一幕出现了,一个新增页面的数据,在我关闭窗口后,再次打开,显示的还是上次编辑的数据,What?,难道是我赋值操作没做好嘛?于是我上上下下,里里外外检查了很多遍,甚至以为是keep-alive组件的缓存数据造成的。直到两个小时后,在我即将要放弃了,将keep-alive组件都去除了,还是出现了这个问题。灵光一闪间,我将{...}去掉了,直接使用新的对象赋值,没有出现问题了!换回{...} 问题又出现了!换成JSON.parse JSON.stringify方式,又可以了!这{...}到底有啥问题!!!
看到这里,你想到了嘛?关键信息其实前面已经提到了。em,灵光乍现,我忽然想到{...}的定义是将该对象下所有的可遍历属性拷贝到新对象中,这里说的对象下的属性,指的是怕是一级属性吧,而我这个复杂对象不知道嵌套了多少层,对象中的属性也是一个对象,这个属性中的对象拷贝,就变成了引用传值了。说到底,这种方式,也不是完全的深拷贝,只能针对没有嵌套的对象,而嵌套了的对象就会出现大问题呢。最终,我换成了JSON方式来拷贝对象,花了两个小时来解决这个问题。折磨了我两个小时,不得不说,前端太难写了
——来自一个写前端的苦逼JAVA程序猿
还没有评论,来说两句吧...