zoukankan      html  css  js  c++  java
  • 深拷贝与浅拷贝的区别,实现深拷贝的几种方法

    JS的基本数据类型
    基本数据类型:String,Boolean,Number,Undefined,Null;
    引用数据类型:Object(Array,Date,RegExp,Function);
    浅拷贝
    浅拷贝是会将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。
    Object.assign、 扩展运算符 ... 、 Array.prototype.slice()、 Array.prototype.concat() 等
    深拷贝
    深拷贝复制变量值,对于引用数据,则递归至基本类型后,再复制。
    深拷贝后的对象与原来的对象是完全隔离的,互不影响,对一个对象的修改并不会影响另一个对象
    示例:
    复制代码
    let obj = {
    name : 'Michael',
    age : 18 ,
    others : {
    hobbies : ['basketball','gambling'],
    team : 'Bulls',
    },
    }
    let [newObj,newObj2] = [Object.assign({},obj),{...obj}]

    obj.name = 'Kobe';
    obj.others.team = 'Lakers';
    
    console.log(obj)
    
    console.log(newObj)
    
    console.log(newObj2)
    

    复制代码

    我们能看出:
    newObj及newObj2的others为引用数据类型,因此与原对象obj的属性值指向同一块内存地址,修改原对象的others内的内容,新对象的内容也会更改
    第一层的属性值为基本数据类型(String),修改原对象,对新对象不造成影响

    因此 深拷贝与浅拷贝的区别为
    浅拷贝当第一层的属性值是基本数据类型时,新的对象和原对象互不影响,但是如果第一层的属性值是引用数据类型时,那么新对象和原对象的属性值其指向的是同一块内存地址。
    深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象;
    深拷贝的代码实现
    如果是基本数据类型,直接返回
    如果是复杂数据类型,递归。
    如果是RegExp 或者 Date 类型,返回对应类型
    复制代码
    let obj = {
    name: 'Michael',
    age: 18,
    others: {
    hobbies: ['basketball', 'gambling'],
    team: 'Bulls',
    },
    }
    // 递归拷贝
    function deepClone(obj) {
    if (obj instanceof RegExp) return new RegExp(obj);
    if (obj instanceof Date) return new Date(obj);
    if (obj === null || typeof obj !== 'object') return obj //如果不是引用类型则直接返回
    let newObj = new obj.constructor(); //如果obj是数组,newObj=[];如果obj是对象,newObj={}
    for (let key in obj) {
    if (obj.hasOwnProperty(key)) { //是否是自身的对象
    newObj[key] = deepClone(obj[key]) //赋值
    }
    }
    return newObj
    }
    const copyObj = deepClone(obj);
    obj.others.team = 'Lakers';
    console.log(obj,copyObj)
    复制代码

    经比较:深拷贝后修改原对象的值并不会改变新对象的值

    壹 ❀ 引

    如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。

    此篇文章中也会简单阐述到栈堆,基本数据类型与引用数据类型,因为这些概念能更好的让你理解深拷贝与浅拷贝。

    我们来举个浅拷贝例子:

    let a=[0,1,2,3,4],
    b=a;
    console.log(a===b);
    a[0]=1;
    console.log(a,b);

    嗯?明明b复制了a,为啥修改数组a,数组b也跟着变了,这里我不禁陷入了沉思。

    那么这里,就得引入基本数据类型与引用数据类型的概念了。

    贰 ❀ 基本数据与复杂(引用)数据

    面试常问,基本数据类型有哪些,number,string,boolean,null,undefined,symbol以及未来ES10新增的BigInt(任意精度整数)七类。

    引用数据类型(Object类)有常规名值对的无序对象{a:1},数组[1,2,3],以及函数等。

    而这两类数据存储分别是这样的:

    a.基本类型--名值存储在栈内存中,例如let a=1;

    当你b=a复制时,栈内存会新开辟一个内存,例如这样:

    所以当你此时修改a=2,对b并不会造成影响,因为此时的b已自食其力,翅膀硬了,不受a的影响了。当然,let a=1,b=a;虽然b不受a影响,但这也算不上深拷贝,因为深拷贝本身只针对较为复杂的object类型数据。

    b.引用数据类型--名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值,我们以上面浅拷贝的例子画个图:

    当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。

    而当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。

    那,要是在堆内存中也开辟一个新的内存专门为b存放值,就像基本类型那样,岂不就达到深拷贝的效果了

    叁 ❀ 实现简单的深拷贝

    1.我们怎么去实现深拷贝呢,这里可以递归递归去复制所有层级属性。

    这么我们封装一个深拷贝的函数(PS:只是一个基本实现的展示,并非最佳实践)

    复制代码
    function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};
    if(obj && typeof obj==="object"){
    for(key in obj){
    if(obj.hasOwnProperty(key)){
    //判断ojb子元素是否为对象,如果是,递归复制
    if(obj[key]&&typeof obj[key] ==="object"){
    objClone[key] = deepClone(obj[key]);
    }else{
    //如果不是,简单复制
    objClone[key] = obj[key];
    }
    }
    }
    }
    return objClone;
    }
    let a=[1,2,3,4],
    b=deepClone(a);
    a[0]=2;
    console.log(a,b);
    复制代码
    可以看到

    跟之前想象的一样,现在b脱离了a的控制,不再受a影响了。

    这里再次强调,深拷贝,是拷贝对象各个层级的属性,可以看个例子。JQ里有一个extend方法也可以拷贝对象,我们来看看

    let a=[1,2,3,4],
    b=a.slice();
    a[0]=2;
    console.log(a,b);

    那是不是说slice方法也是深拷贝了,毕竟b也没受a的影响,上面说了,深拷贝是会拷贝所有层级的属性,还是这个例子,我们把a改改

    let a=[0,1,[2,3],4],
    b=a.slice();
    a[0]=1;
    a[2][0]=1;
    console.log(a,b);

    拷贝的不彻底啊,b对象的一级属性确实不受影响了,但是二级属性还是没能拷贝成功,仍然脱离不了a的控制,说明slice根本不是真正的深拷贝。

    这里引用知乎问答里面的一张图

    第一层的属性确实深拷贝,拥有了独立的内存,但更深的属性却仍然公用了地址,所以才会造成上面的问题。

    同理,concat方法与slice也存在这样的情况,他们都不是真正的深拷贝,这里需要注意。

    2.除了递归,我们还可以借用JSON对象的parse和stringify

    复制代码
    function deepClone(obj){
    let _obj = JSON.stringify(obj),
    objClone = JSON.parse(_obj);
    return objClone
    }
    let a=[0,1,[2,3],4],
    b=deepClone(a);
    a[0]=1;
    a[2][0]=1;
    console.log(a,b);
    复制代码

    可以看到,这下b是完全不受a的影响了。

    附带说下,JSON.stringify与JSON.parse除了实现深拷贝,还能结合localStorage实现对象数组存储。有兴趣可以阅读博主这篇文章。

    localStorage存储数组,对象,localStorage,sessionStorage存储数组对象

    3.除了上面两种方法之外,我们还可以借用JQ的extend方法。

    $.extend( [deep ], target, object1 [, objectN ] )

    deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝

    target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。

    object1 objectN可选。 Object类型 第一个以及第N个被合并的对象。

    let a=[0,1,[2,3],4],
    b=$.extend(true,[],a);
    a[0]=1;
    a[2][0]=1;
    console.log(a,b);
    可以看到,效果与上面方法一样,只是需要依赖JQ库。

    说了这么多,了解深拷贝也不仅仅是为了应付面试题,在实际开发中也是非常有用的。例如后台返回了一堆数据,你需要对这堆数据做操作,但多人开发情况下,你是没办法明确这堆数据是否有其它功能也需要使用,直接修改可能会造成隐性问题,深拷贝能帮你更安全安心的去操作数据,根据实际情况来使用深拷贝,大概就是这个意思。

  • 相关阅读:
    华强北三代悦虎1562A怎么样?
    改丝印的假华强北三代1562A,用芯良苦!
    华强北三代过软件检测的佳和1562A
    Unlua静态导出
    Unlua编程基础
    Android JNI调用
    手机屏幕参数
    UE4 stats性能埋点
    【JWT】JSON Web Token
    【算法】一致性哈希算法实现
  • 原文地址:https://www.cnblogs.com/dillonmei/p/12578545.html
Copyright © 2011-2022 走看看