从Vue赋值问题到JavaScript深拷贝

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

从Vue赋值问题到JavaScript深拷贝
图一:进行编辑前
从Vue赋值问题到JavaScript深拷贝
图二:点击编辑修改数据

上图的代码实现逻辑是,点击编辑,将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

(3)
witersen的头像witersen
上一篇 2021年1月28日 上午11:09
下一篇 2021年1月28日 下午3:49

相关推荐

发表回复

登录后才能评论