ES6的解构赋值、Object.assign、[].concat它们是深拷贝还是浅拷贝?

前言

在开发项目的时候,发现用ES6的解构赋值和[].contant对数组进行新的赋值时,发现还是会影响到旧数组的数据。无奈之下,最后用了JSON.parse(JSON.stringify(obj))进行深拷贝。

先认识一下深拷贝和浅拷贝的定义

深拷贝:修改新变量的值不会影响原有变量的值。默认情况下基本数据类型(number,string,null,undefined,boolean)都是深拷贝。

浅拷贝:修改新变量的值会影响原有的变量的值。默认情况下引用类型(object)都是浅拷贝。

对深拷贝和浅拷贝有了简单的认识了,下面我们开始今天的旅程吧。

ES6的解构赋值

下面开看一个简单的例子:

var obj = {
    name: "张三",
    age: 18
}

var obj1 = {...obj}

obj1.age = 20

console.log(obj,obj1)
复制代码

打印结果:

image.png

很多伙伴就会说,这是深拷贝啊

那我们再看看一个例子:

var obj = {
    name: "张三",
    age: 18,
    info: {
        company: "周星星公司"
    }
}

var obj1 = {
    ...obj
}

obj1.info.company = "朱茵公司"

console.log(obj, obj1)
复制代码

打印结果:

image.png

是不是很意外,是不是很惊喜。这是为什么,这不是深拷贝吗?why?

发现解构赋值出来的对象将原对象obj中的company的数据修改了,这样看还是浅拷贝。

Object.assign

这个之前有个伙伴跟我跟,他可以深拷贝和浅拷贝,我之前以为一直都是浅拷贝而已。

只要这样就可以进行深拷贝了:

Object.assign({},obj)
复制代码

确实这样能达到深拷贝,可以看下面一个例子:

var obj = {
    name: "周星星",
    age: 18
}

var obj1 = Object.assign({},obj)

obj1.age = 20

console.log(obj, obj1)
复制代码

打印结果:

image.png

这是为什么呢,因为我们重新定义了一个空对象{},在空数组里面添加新的属性,因为Object.assign中前对象会被后对象的值所覆盖。确实让我认识到了一个新写法。

但是,我们再看看这个例子:

var obj = {
    name: "周星星",
    age: 18,
    info:{
        company:"周星星的公司"
    }
}

var obj1 = Object.assign({},obj)

obj1.info.company = "朱茵的公司"

console.log(obj, obj1)
复制代码

image.png

惊喜总是那么的意外啊。依旧是company修改了,合着旧数据一起被改掉了,还是浅拷贝。

[].concat

依旧我们先来个例子:

var list = [1,2,3]
var list1 = [].concat(list)

list1[1] = 9

console.log(list, list1)
复制代码

打印结果:

image.png
有人看到这个第一眼也会说,这是个深拷贝,这可以解决问题,那我就用这个吧

那我们再来看看这个例子:

var list = [{
    name: "周星星",
    age: 18
}, {
    name: "朱茵",
    age: 16
}]
var list1 = [].concat(list)

list1[1].age = 9

console.log(list, list1)
复制代码

打印结果:

image.png

这是为什么啊,我明明给了个新数组了,改的也是新数组的数据,但是旧数据还是会变的呢,最终还是个浅拷贝

总结

  • 如果所解构的原对象是一维数组或对象,其本质就是对基本数据类型进行等号赋值,那它们就是深拷贝;
  • 如果是多维数组或对象,其本质就是对引用类型数据进项等号赋值,那它们就是浅拷贝;

结论:它们都是浅拷贝(因为它确实不能对多维数组或对象达到深拷贝的作用);

如果需要使用它们用作深拷贝,需要保证是一维的。

拓展-深拷贝方法

递归

原理:我们在拷贝的时候判断一下属性值的类型,如果是对象,我们递归调用深拷贝函数就好了

let deepCopy = function (obj) {
    // 只拷贝对象
    if (typeof obj !== 'object') return;
    // 根据obj的类型判断是新建一个数组还是对象
    let newObj = obj instanceof Array ? [] : {};
    // 遍历obj,并且判断是obj的属性才拷贝
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            // 如果obj的子属性是对象,则进行递归操作,否则直接赋值
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}
复制代码

JSON.parse(JSON.stringify(obj))

为什么前面会说无奈之举才会有用JSON.stringify和JSON.parse进行深拷贝呢?

这行代码的运行过程,就是利用 JSON.stringify 将js对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象;序列化的作用是存储和传输。(对象本身存储的是一个地址映射,如果断电,对象将不存在,所以要将对象的内容转换成字符串的形式再保存在磁盘上)
不过,这种实现深拷贝的方法有局限性,它只适用于一般数据的拷贝(对象、数组),有以下情况需要注意:

1.如果json里面有时间对象,则序列化结果:时间对象=>字符串的形式;

{
    let obj = {
        age: 18,
        date: new Date()
    };
    let objCopy = JSON.parse(JSON.stringify(obj));
    console.log('obj', obj);
    console.log('objCopy', objCopy);
    console.log(typeof obj.date); // object
    console.log(typeof objCopy.date); // string
}
复制代码

image.png

2.如果json里有RegExp、Error对象,则序列化的结果将只得到空对象 RegExp、Error => {};

{
    let obj = {
        age: 18,
        reg: new RegExp('\w+'),
        err: new Error('error message')
    };
    let objCopy = JSON.parse(JSON.stringify(obj));
    console.log('obj', obj);
    console.log('objCopy', objCopy);
}
复制代码

image.png

3.如果json里有 function,undefined,则序列化的结果会把 function,undefined 丢失;

{
    let obj = {
        age: 18,
        fn: function () {
            console.log('fn');
        },
        hh: undefined
    };
    let objCopy = JSON.parse(JSON.stringify(obj));
    console.log('obj', obj);
    console.log('objCopy', objCopy);
}
复制代码

image.png

4.如果json里有NaN、Infinity和-Infinity,则序列化的结果会变成null;

{
    let obj = {
        age: 18,
        hh: NaN,
        isInfinite: 1.7976931348623157E+10308,
        minusInfinity: -1.7976931348623157E+10308
    };
    let objCopy = JSON.parse(JSON.stringify(obj));
    console.log('obj', obj);
    console.log('objCopy', objCopy);
}
复制代码

image.png

5.如果json里有对象是由构造函数生成的,则序列化的结果会丢弃对象的 constructor;

{
    function Person(name) {
        this.name = name;
    }
    let obj = {
        age: 18,
        p1: new Person('lxcan')
    };
    let objCopy = JSON.parse(JSON.stringify(obj));
    console.log('obj', obj);
    console.log('objCopy', objCopy);
    console.log(obj.p1.__proto__.constructor === Person); // true
    console.log(objCopy.p1.__proto__.constructor === Object); // true
}
复制代码

image.png

6.如果对象中存在循环引用的情况也无法实现深拷贝

{
    let obj = {
        age: 18
    };
    obj.obj = obj;
    let objCopy = JSON.parse(JSON.stringify(obj));
    console.log('obj', obj);
    console.log('objCopy', objCopy);
}
复制代码

image.png

以上,如果拷贝的对象不涉及上面的情况,可以使用 JSON.parse(JSON.stringify(obj)) 实现深拷贝。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享