最近 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 的对象属性. 而且, 二者不指向同一个对象.
很明显, 深拷贝比较符合我这次的业务需求. 深拷贝, 比较笨一点的办法就是将自己需要的数据自己封装起来.
- let object={
- repayment:this.ruleForm.repayment,
- interestType:this.ruleForm.interestType,
- productDeadline:this.ruleForm.productDeadline,
- circumstancesOfDetention:this.ruleForm.circumstancesOfDetention,
- }
- this.tableData.push(object);
但是, 这样明显会使代码很臃肿, 而且, 这还是在需要的数据只有 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 字符串中这个键值对会消失, 因为不支持.
所以, 这个时候笨的办法也是有好处的, 就是面对一些特殊的类型, 或是对象属性复杂的情况下, 因为自己对程序的需求比较了解, 就可以按照自己的需要进行封装. 不管黑猫白猫, 能抓到老鼠的就是好猫.
来源: https://www.cnblogs.com/jdWu-d/p/11959800.html