最近做Vue的项目发现这样的现象:对象间进行赋值操作后,原对象数据会随赋值后的对象数据值的变化而变化。如图一图二所示:


上图的代码实现逻辑是,点击编辑,将A对象(表格数据)的值赋值给B对象(弹出框表单数据),所以按照常规思维,就算Vue的数据都是双向绑定,但是我们编辑第一个对象的值,又怎么会影响第二个对象的值呢,原来要联系到JavaScript的对象赋值原理,此处的赋值方式只是将B对象的地址也指向了A对象的地址,再加上Vue双向绑定的特点,就会出现这种编辑表单,页面列表中的数据也随之联动的现象。
看到网上很多文章都提供这样一种方法:既然你的赋值方式为只改变地址指向,那我就想办法让B对象也就是被赋值对象必须为新创建的对象,这样就能保证两个对象不会指向同一个地址了,具体实现为将 JSON.parse 方法与 JSON.stringify结合使用:
this.B = JSON.parse(JSON.stringify(this.A))
但是上面的说法是比较片面的
JavaScript中的数据类型分为两大类:值类型(基本类型)和引用数据类型
值类型包括:字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol
引用数据类型包括:对象(Object)、数组(Array)、函数(Function)
我们首先看下值类型的拷贝:
var a = 3;
var b = a;
b = 5;
console.log(a); // 3
console.log(b); // 5
再看下引用类型拷贝:
var obj1 = {
a: 1,
b: 2,
c: 3
}
var obj2 = obj1;
obj2.a = 5;
console.log(obj1.a); // 5
console.log(obj2.a); // 5
我们将 obj1 赋予 obj2 的时候,我们其实仅仅只是将 obj1 存储在栈堆中的的引用赋予了 obj2 ,而两个对象此时指向的是在堆内存中的同一个数据,所以当我们修改任意一个值的时候,修改的都是堆内存中的数据。这就是简单的浅拷贝例子
接下来是深拷贝:
深拷贝不会拷贝引用类型的引用,而是将引用类型的值全部拷贝一份,形成一个新的引用类型,这样就不会发生引用错乱的问题,使得我们可以多次使用同样的数据,而不用担心数据之间会起冲突
看一个简单的深拷贝例子:
var obj1 = {
a: 1,
b: 2,
c: 3
}
var objString = JSON.stringify(obj1);
var obj2 = JSON.parse(objString);
obj2.a = 5;
console.log(obj1.a); // 1
console.log(obj2.a); // 5
为什么使用 JSON.stringify 与 JSON.parse 结合可以实现深拷贝呢?
原因:利用JSON.stringify 将js对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象;序列化的作用是存储(对象本身存储的只是一个地址映射,如果断电,对象将不复存在,因此需将对象的内容转换成字符串的形式再保存在磁盘上 )和传输(例如 如果请求的Content-Type是 application/x-www-form-urlencoded,则前端这边需要使用qs.stringify(data)来序列化参数再传给后端,否则后端接受不到)
使用 JSON.stringify 与 JSON.parse 结合实现深拷贝也有很多不足之处:
- 如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象
- 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象
- 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失
- 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
- JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor
- 如果对象中存在循环引用的情况也无法正确实现深拷贝
所以如果遇到拷贝多层对象,使用JSON.stringify 与 JSON.parse 结合并不能真正实现完全拷贝,可用以下方法实现
//实现深拷贝函数
function deepClone(data) {
const type = this.judgeType(data);
let obj = null;
if (type == 'array') {
obj = [];
for (let i = 0; i < data.length; i++) {
obj.push(this.deepClone(data[i]));
}
} else if (type == 'object') {
obj = {}
for (let key in data) {
if (data.hasOwnProperty(key)) {
obj[key] = this.deepClone(data[key]);
}
}
} else {
return data;
}
return obj;
}
function judgeType(obj) {
// tostring会返回对应不同的标签的构造函数
const toString = Object.prototype.toString;
const map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object',
};
if (obj instanceof Element) {
return 'element';
}
return map[toString.call(obj)];
}
const test = {
name: 'a',
date: [1,2,3]
};
console.log(deepClone(test))
test.date[0] = 6;
console.log(test);
参考文章:
https://blog.csdn.net/u013565133/article/details/102819929
https://www.jianshu.com/p/f4329eb1bace
原创文章,作者:witersen,如若转载,请注明出处:https://www.witersen.com