js 浅拷贝 & 深拷贝

890 阅读3分钟

深拷贝/浅拷贝

4-1

赋值

数据的复制有两种情况,一直是值复制,一种是引用赋值({},[],function)

var a = 2;
var b = a;
a = 4;
console.log(b)//2  值复制,a的值改变不影响b

//接下来看引用类型的复制
var arr1 = {a: 1};
var arr2 = arr1;
console.log(a === b); // 输出true
console.log(arr2.a);// 1
arr1.a = 2;
console.log(arr2.a);// 2

一般我们的利用“=”赋值只是单纯的引用赋值

浅拷贝

先说结论:浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的数据

我按照两个方面来整理

  • 1. 仅仅针对数组类型的浅拷贝方法

    • slice()方法:他是将原数组中抽离部分出来形成一个新数组。我们只要设置为抽离全部,即可完成数组的浅拷贝
    • concat()方法:这个代码也非常简单,原理更加粗暴。它是用于连接多个数组组成一个新的数组的方法。那么,我们只要连接它自己,即可完成数组的浅拷贝
  • 2. 可以对数组以及对象的浅拷贝方法

    • hasOwnProperty方法
    • es6的扩展运算符
仅仅针对数组类型的浅拷贝方法

var arr0 = [1,2,[3,4]];
var arr3 = arr0.slice();//slice方法

//concat方法
// var arr3 = arr0.concat();

//es6的扩展运算符
var [ ...arr3 ] = arr;


console.log(a === b);  // 输出false,说明外层数组拷贝的是实例,不是引用
arr3[0] = 100;
console.log(arr0[0]);// 1
console.log(arr3[0]);// 100

//    如果我们修改arr0的第二层呢,也就是说属性1,2,[3,4]是第一层数据,而[3,4]还是引用类型的,就是第二层数据
arr3[2][0] = 99;
console.log(arr0[2][0]);//99  发现obj0也改变了
console.log(arr3[2][0]);//99  
可以对数组以及对象的浅拷贝方法

我们可以考虑用对象的hasOwnProperty方法来进行浅拷贝,可以对数组[],对象{}进行浅拷贝

    var obj2 = {
      a: 12, 
        b: function() {
        console.log('1');
        },
        'c' : [1,[2,3],[4,5]],
    };

//hasOwnProperty方法
    var obj3 = shallowCopy(obj2);

    function shallowCopy(src) {
        var dst = {};
        for (var prop in src) {
            if (src.hasOwnProperty(prop)) {
                dst[prop] = src[prop];
            }
        }
        return dst;
    }
    
// ES6的扩展运算方法
var {...obj3} = obj2;//如果obj2是对象
var [...obj3] = obj2;//如果obj2是数组



//step1:     改变属性a,属性a是单纯的基础类型
    obj3.a = "mike3";
    console.log(obj1.a);//mike2
    console.log(obj3.a);//mike3  可以发现其实obj1的a是实例拷贝,而不是引用拷贝
    
    
// step2:    改变属性b(属性b是函数)
    obj3.b = function() {
        console.log('改变后的');
    };
    console.log(obj3.b); 
    //    ƒ () {
    //        console.log('改变后的');
    //    }
    
    console.log(obj2.b);  // 改变obj3的属性b,但是obj2的b不变
    //    ƒ () {
    //        console.log('1');
    //    }
    
   
// step3:   改变属性c,属性c是数组,一个引用类型
//    如果我们修改obj1的第二层呢,也就是说属性a,b,c是第一层数据,而c的值还是引用类型的话,那c的属性值就是第二层数据
    obj3.c[0] = 99;
    console.log(obj1.c);//[99, [2,3],[4,5]]   obj1.c也改变了
    console.log(obj3.c);//[99, [2,3],[4,5]]
    //可以发现:第二层的数据的拷贝依旧是引用拷贝!!

  • 这种情况,为什么obj3的a改变,obj2的a没有改变,其实是拷贝成功了,把引用类型进行实例的拷贝(也就是值复制);
  • obj2的属性b是一个函数,但是该方法也可以对其进行值拷贝;我没太弄明白?
  • 但是obj2的属性c又是一个引用类型。可以发现,浅拷贝仅仅是对外层的进行实例拷贝,如果其属性元素的值(那就是内层的子数组)为引用类型的值时,则内层元素仍然是拷贝引用。

常用方法为:Array.prototype.slice(), Array.prototype.concat(), jQury的$.extend({},obj),例:

var a = [{c:1}, {d:2}];
var b = a.slice();

a[0].c = 3;
console.log(b[0].c); // 输出 3,说明其元素拷贝的是引用

4-2

深拷贝

深拷贝后,两个对象,包括其内部的元素互不干扰。常见方法有JSON.parse(),JSON.stringify(),jQury的$.extend(true,{},obj),lodash的_.cloneDeep和_.clone(value, true);但是各有不同

  • 1. 仅对对象{}进行浅拷贝方法
    • 转换成json再转换成对象实现对象的深拷贝
    var obj = {
        name: 'man',
        other: {
          a: 1
        }
    }

    var obj2 = JSON.parse(JSON.stringify(obj))

    console.log(obj2.other)// {a: 1}
    obj2.other.a = 2;
    console.log(obj.other);// {a: 1} 可以看出,修改obj2的第二层的数据,obj依旧不变,所以是深拷贝
    console.log(obj2.other);//{a: 2}
  • 还有其他的深拷贝方法 Underscore —— _.clone()/jQuery —— .clone() /.extend()/lodash —— _.clone() / _.cloneDeep() 这里不细说了,毕竟这些个封装好的,我们可以调用该函数