前端必备小知识之深、浅拷贝

591 阅读6分钟

前言

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。你会 Copy 吗?在很多实际的工作场合中,前端攻城狮们在处理数据的时候,经常会使用到数据的深拷贝和浅拷贝,今天,就由我向大家分享一下前端程序员必备小知识之数据的深拷贝和浅拷贝,希望对大家在工作、学习过程中有帮助!

应用场景

当前台获取到后台传递的数据时,你需要将这个数据当做参数发送给另一个接口的时候,多多少少不多不少,就是有那么几个剩余的字段是多余的,请问,这时你该怎么办呢?

不知名的掘友:那这还不简单,直接删掉多余参数不就行了嘛,so easy 啦!

答:

  • 如果删除后你后面又要原来的数据字段呢?这样一来不仅会造成页面数据的丢失和污染,页面上的某些内容还会出现无法显示的情况
  • 如果不做处理的话,接口接收到多余的参数可能会引发 Bug ,造成请求错误,无法获取正确的响应数据
  • 所以,在这种情形之下深拷贝浅拷贝就非常必要!

概念:浅拷贝与深拷贝

何为浅拷贝与深拷贝呢?

我的个人理解

  • 有两个对象 A 与 B, A 对象不为空 B 为空,B 复制了 A,我们修改 A,如果 B 中的数据跟着变化了,那就是浅拷贝
  • 如果没有变化,那就是深拷贝
  • 简单的说就是,浅拷贝会影响之前被拷贝对象的数据,而深拷贝则不会

注意:赋值操作和深拷贝以及浅拷贝不是一回事,不要混淆概念哦!

深拷贝和浅拷贝主要针对于引用类型数据,因为基本数据类型赋值之后,改变了新数据并不会影响到原来的数据,他们之间互不干扰、各自独立;而对于引用数据类型赋值后,改变新数据将会影响到原来的数据,此时应该使用浅拷贝和深拷贝定义出一个与原数据一样但互不影响的数据。

区别与本质

因为数据都是存储在内存当中,而我们调用数据的时候都是通过地址来调用数据的

对于浅拷贝来说,比如一个数组(对象),只要我们修改了一个拷贝数组,那么原数组也会受到影响,这是因为他们引用的是同一个地址的数据,拷贝的时候并没有给拷贝数组创造独立的内存,只是把被拷贝数组指向数据的地址拷贝给了拷贝数组。

深拷贝就与其相反,将会给拷贝数组创造独立的内存,并且将被拷贝数组的内容一一拷贝进去,两者互不影响、相互独立!

实践部分

基本数据类型的赋值操作

JS 中的数据类型 (JavaScript 中的数据类型分为 原始类型对象类型 ),只有7种内置类型:nullundefinedbooleannumberstringbigintsymbol。基本数据类型包括:numberstringbooleanundefinednull,基本数据类型之间赋值后两个变量互不影响。

    let a = 666;
    let b = a; 
    a = 777;
    
    console.log(a); // 777
    console.log(b); // 666

通过 a 的赋值给 bb 的值为 666,之后,又改变了 a 的值,但是并不影响 b 空间的值。

引用数据类型的赋值

    let arr = [1, 2, 3];
    let anotherArr = arr;
    arr.pop(2);
    console.log(arr); // (2) [1, 2]
    console.log(anotherArr); // (2) [1, 2]
    console.log(arr === anotherArr); // true

这里初始化一个名为 arr 的数组,将 arr 赋值给 anotherArr 数组,此时,arranotherArr 已经共享了一个内存地址,后面将 arrindex = 2 位置上的元素删除后,anotherArr 也发生改变,最终输出的结果也是一毛一样,使用 === 判断值及类型是否完全相等,结果引用地址也完全一致。

浅拷贝

可以使用系统提供的构造函数 Object 上的 assign 方法,Object.assign()  方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。 后面的源对象的属性将类似地覆盖前面的源对象的属性。

语法:

    Object.assign(target(目标对象), ...sources(源对象))

举个栗子:

    const target = { a: 1, b: 2 };
    const source = { b: 4, c: 5 };

    const returnedTarget = Object.assign(target, source);

    // 试着改变拷贝对象
    returnedTarget.b = 10;
    
    // 结果随之也发生了改变
    console.log(target); // Object { a: 1, b: 10, c: 5 }
    console.log(returnedTarget); // Object { a: 1, b: 10, c: 5 }
    console.log(target === returnedTarget); // true

针对深拷贝,需要使用其他办法,因为 Object.assign()拷贝的是 (可枚举)属性值。

深拷贝

此时路过的掘友:那如何实现深拷贝呢?

这里给出两个方法,供大家参考学习:

    // 深克隆(深克隆不考虑函数)
    function deepClone(obj, result) {
        var result = result || {};
        for (var prop in obj) {
            if (obj.hasOwnProperty(prop)) {
                if (typeof obj[prop] == 'object' && obj[prop] !== null) {
                    // 引用值(obj/array)且不为null
                    if (Object.prototype.toString.call(obj[prop]) == '[object Object]') {
                        // 对象
                        result[prop] = {};
                    } else {
                        // 数组
                        result[prop] = [];
                    }
                    deepClone(obj[prop], result[prop])
                } else {
                    // 原始值或func
                    result[prop] = obj[prop]
                }
            }
        }
        return result;
    }

    // 深浅克隆是针对引用值
    function deepClone(target) {
        if (typeof (target) !== 'object') {
            return target;
        }
        var result;
        if (Object.prototype.toString.call(target) == '[object Array]') {
            // 数组
            result = []
        } else {
            // 对象
            result = {};
        }
        for (var prop in target) {
            if (target.hasOwnProperty(prop)) {
                result[prop] = deepClone(target[prop])
            }
        }
        return result;
    }

以上两个函数,一个函数有两个参数一个函数有一个参数,二者都以递归的方式实现了数据的深克隆,做到了无论更改哪个拷贝对象的都不会影响另外一个,两者真正地相互独立且互不影响。

总结

  1. 赋值是赋值,拷贝是拷贝,不要将赋值和拷贝弄混淆,两者不是一码事

  2. 浅拷贝,当对象或数组中的数据都是基本数据类型的时候,两个数据之间完全是独立的,如果对象或数组中的值是引用类型的时候,里面是引用类型的值,还是会保持共同的内存地址

  3. 浅拷贝常用系统提供的构造函数 Object 上的 assign 方法

  4. 深拷贝需要自己写函数去加以实现,且深拷贝出来的两个数据是完全独立互不影响

  5. 不能照搬照抄,要学以致用,深、浅拷贝具体的运用需要结合具体情况使用

结尾

撰文不易,欢迎大家点赞、评论,你的关注、点赞是我坚持的不懈动力,感谢大家能够看到这里!Peace & Love。