深拷贝与浅拷贝

目录

一、数据类型:

二、浅拷贝:

三、深拷贝:


一、数据类型:

基本数据类型:String、Boolean、Undefined、null、Number。

引用数据类型:Object(函数Functi   on和数组Array是一种特别的对象)。

代码演示:

基本类型的复制:从一个变量向另外一个新变量复制基本类型的值,会创建这个值的一个副本,并将该副本复制给新变量。

    let foo = 1;
    let bar = foo;
    console.log(foo===bar);
//    修改foo变量的值并不会影响bar变量的值
    foo = 2;
    console.log("foo---"+foo);
    console.log("bar---"+bar);


 引用类型的复制:从一个变量向另一个新变量复制引用类型的值,其实复制的是指针,最终两个变量都指向同一个对象。

    let foo1 = {
        name:"luobotou",
        age:20
    }
    let bar1 = foo1;
    console.log(foo1==bar1);

//    改变foo1变量的值会影响bar1变量的值
    foo1.age = 19;
    console.log(foo1);
    console.log(bar1);


二、浅拷贝:

1. 仅仅是复制了引用,彼此之间的操作会互相影响。

2. 对对象而言,它的第一层属性值如果是基本数据类型则完全拷贝一份数据,如果是引用数据类型就拷贝内存地址值。

代码演示:

(1)对象浅拷贝:创建一个对象me把对象me的数据通过connect方法拷贝到对象me2中。

    var me = {
        name:"萝卜头",
        age:19,
        address:{
            home:"HeNan"
        }
    };

    var me2 = {
        sex:"男"
    }
    
    function connect(old,n){
        var n = n || {};
        for(var i in old){
            n[i]  =old[i]
        }
    }

    connect(me,me2);

    console.log("修改值前---------");
    console.log(me);
    console.log(me2);

    console.log("修改值后---------");
    me.name="大萝卜头"
    console.log(me);
    console.log(me2);

  可以看到老的对象已经完全拷贝到新的对象中。这是因为浅拷贝对于对象而言:它的第一层属性值如果是基本数据类型则完全拷贝一份数据,如果是引用数据类型就拷贝内存地址值。

 拷贝完修改老的对象里面的name,但是新的对象却没有改变。


 

 (2)通过Object.assign():方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象。第一个参数是目标对象后面的参数都是源对象。

let obj1 = {
        name:"萝卜头",
        res:{
            value:123
        }
    }

    let obj2 = Object.assign({},obj1);
    obj2.res.value = 456;
    console.log("是引用数据类型就拷贝内存地址");
    console.log(obj2);
    console.log(obj1);
    console.log("第一层属性值是基本数据类型则完全拷贝一份数据");
    obj2.name='haha';
    console.log(obj2);
    console.log(obj1);
 

(3)ES6的扩展运算符(spread)三个点:

    let obj1 = {
        name:"萝卜头",
        res:{
            value:123
        }
    }

    let {...obj2} = obj1;

    obj2.res.value = 456;
    console.log(obj2);
    console.log(obj1);
    obj2.name="haha"
    console.log(obj2);
    console.log(obj1);

修改一个对象里面的value值,两个对象都会发生改变,因为其地址值相同。


 修改一个对象的里面的简单数据类型的值另一个对象不会发生改变,因为是完全拷贝一份数据。

 

 


 (4)Array.prototype.slice([begin[,end]]):

begin:提取起始处的索引(从0开始),从该索引开始提取原数组元素。如果该参数为负数,则表示从原数组中倒数第几个元素开始提取,slice(-3)表示提取原数组倒数第三个到最后一个元素(包含最后一个元素)。

如果省略begin,则slice从索引0开始。

如果begin超出原数组的索引范围,则会返回空数组。

end:提取终止处的索引(从0开始),在该索引处结束提取原数组元素。slice会提取原数组中索引begin到end的所有元素(包含begin,但不包含end)。如果该参数为负数,则表示从原数组中倒数第几个元素开始提取,slice(-3,-1)表示提取原数组倒数第三个到最后一个元素(不包含最后一个元素)。

如果end被省略,则slice会一直提取到原数组末尾。

如果end大于数组的长度,slice也会一直提取到原数组末尾。

 

    console.log("Array.prototype.slice");
    const arr1 = [
        "萝卜头",
        {
            value:123
        }
    ];

    const arr2 = arr1.slice(0);

    arr2[0] = "大萝卜头";
    console.log(arr1);
    console.log(arr2);

 

 slice 不会修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。原数组的元素会按照下述规则拷贝:

如果该元素是个对象引用 (不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。

对于字符串、数字及布尔值来说(不是 StringNumber 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。

资料参考:MDN


 (5)Arrays.property.concat():concat()方法用于合并两个或多个数组。此方法不更改现有数组,而是返回一个新数组。

concat方法不会更改this或作为参数提供的任何数组,而是返回一个浅表副本,其中包含与原始数组组合的相同元素的副本。原始数组的元素被复制到新数组中 。

    const arr1 = [
        '萝卜头',
        {
            value:123
        }
    ];

    const arr2 = [].concat(arr1);
    
    arr2[1].value = 456;
    arr2[0] = "哈哈";
    console.log(arr1);
    console.log(arr2)

 

 

对象引用(而不是实际对象):将concat对象引用复制到新数组中。原始数组和新数组都引用同一对象。也就是说,如果修改了引用的对象,则更改对新数组和原始数组均可见。这包括也是数组的数组参数元素。

数据类型,例如字符串,数字和布尔值(不是 StringNumberBoolean对象): concat将字符串和数字的值复制到新数组中。

 资料参考:MDN。

 三、深拷贝:

1. 深拷贝就是不管是基本数据类型还是引用数据类型都重新拷贝一份,不存在共用数据的现象。

2. 考虑到我们要拷贝的对象不知道有多少层深度,我们可以用递归来解决问题。如果是简单数据类型,无需拷贝,直接返回。如果是引用类型,创建一个对象,遍历需要克隆的对象,将需要克隆的对象的属性执行深拷贝后依次添加到新对象上。

JS数组中实现深拷贝的方法有多种,比如JSON.parse(JSON.stringify())和递归以及JQuery库的extend方法 都是可以实现数组和对象的深拷贝的。但是光这一点是远远不够的。

    var arr1 = ['red','green'];
    var arr2 = JSON.parse(JSON.stringify(arr1)); //复制
    console.log(arr2);

    console.log("改变color1的值");
    arr1.push("black");

    console.log(arr2);
    console.log(arr1);

 


 

(1)最简单的深拷贝: 用递归解决问题,但是有很多的缺陷(没有考虑数组、循环引用等问题)。

    const target = {
        field1: 1,
        field2: undefined,
        field3: '萝卜头',
        field4: {
            child: 'child',
            child2: {
                child2: 'child2'
            }
        }
    };

    function clone(target){
        if(typeof target === "object"){
//            创建一个对象
            let cloneTarget = {};
//            遍历需要克隆的对象
            for(const key in target){
                cloneTarget[key] = clone(target[key])
            }
            return cloneTarget
        }else{
//            如果是原始类型,无需拷贝,直接返回。
            return target;
        }
    }

    bb = clone(target);//先克隆
    target.field1="2"//在改变原来值
    console.log(target); //file1:2
    console.log(bb);//file1:1
 

 

 


(2) 考虑数组。

第一种:

    const target = {
        field1: 1,
        field2: undefined,
        field3: {
            child: 'child'
        },
        field4: [2, 4, 8]
    };

    function clone(target){
        let cloneTarget = Array.isArray(target)?[]:{};
        if(typeof target === "object"){

            for(const key in target){
                cloneTarget[key] = clone(target[key])
            }
            return cloneTarget;
        }else{
            return target;
        }
    }

    bb = clone(target);//先克隆
    target.field1="8";
    target.field4[0]=1;
    console.log(target);
    console.log(bb);

 

 改变原target里面的field1和field4里面的数组值,克隆的bb里面的值并不会发生改变。

 第二种:

function deepClone(obj){
    let objClone = obj instanceof Object?[]:{};
    if(obj && typeof obj === "object"){
        for(key in obj){
            if(obj.hasOwnProperty(key)){
//                    判断obj子元素是否为对象,如果是,递归复制
                if(obj[key] && typeof obj[key] === "obj"){
                    objClone[key] = deepClone(obj[key]);
                }else{
//                    如果不是,简单复制
                        objClone[key] = obj[key];
                }
            }

        }
    }
    return objClone;
}

var a = {
    x:1,
    y:2
}

b = deepClone(a);

console.log(b);

a.x = 3;
console.log(a);
console.log(b);

 

(3)考虑循环引用问题。

什么是循环引用?

举个简单的例子:

    var obj = {};
    obj.b = obj;

当我们深拷贝obj对象时,就会循环的遍历b属性直到栈溢出。

 我们的解决方案为:额外开辟一个存储空间,来存储当前对象和拷贝关系对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话就继续拷贝。

    function clone(target,map = new Map()){

//map是:额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系

        if(typeof target === "object"){

// /拷贝对象
            let cloneT = Array.isArray(target)?[]:{};

// get() 方法用来获取一个 Map 对象中指定的元素。
//当需要拷贝关系时,先去存储空间找,有没有拷贝过这个对象,如果有的话直接返回
            if(map.get(target)){
                return map.get(target)
            }
// set是添加指定的键和元素  没有就继续拷贝。
            map.set(target,cloneT);

            for(const key in target ){
                cloneT[key] = clone(target[key],map);
            }
            return cloneT;
        }else{
            return target;
        }
    }

    var A = {a:1};
    A.A = A;

    var B = clone(A);
    console.log(B);
 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值