最近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方法实现浅拷贝
深拷贝