js 对象深拷贝&浅拷贝

对象深拷贝和浅拷贝的区别:

浅拷贝: 赋值对象的引用,而不是对象的本身;
深拷贝: 把复制的对象所引用的全部对象都复制一遍;

- 解决方案:

关于复杂数据类型的拷贝问题,在实际工作过程中,用到的还是比较多的,之前有试过Object.assign()和JSON.parse( JSON.stringify(obj))等方法,发现其实这两种方法都有弊端,所以今天特意总结和大家分享一个封装的方法,可以解决复杂数据的深拷贝问题,算是比较完善的解决方案,如果任何改善的建议,欢迎留言;

//  通过对传入数据类型的判断及for in 实现比较完善的深拷贝方法
function deepCopy (obj) {
    // 简单数据类型或者null直接return obj
    if (obj == null || typeof (obj) != 'object') { 
        return obj
    }

    var type = obj.constructor;
      // Date或RegExp也直接return对应值
    if(type == Date) {   return new Date(obj)    }

    if(type == RegExp) {  return new RegExp(obj)  }

    var temp = obj.constructor()
    for (var key in obj) {
            // 不遍历其原型链上的属性
        if (obj.hasOwnProperty(key)) {
            // 注意这里的递归调用
            temp[key] = deepCopy(obj[key]);
        }
    }
    return temp;
}

// 示例 : 
var obj = {
        name:'wangfei',
        fun:function(){
            console.log('fun')
        },
        arr:[1,2,{location:{
            nation:['china','japan']
        }}],
        data:undefined,
  }
// 调用方法对数据进行拷贝;
var copyObj = deepCopy(obj);

// 修改拷贝后的值;
copyObj .arr[0] = '啊哈哈';
copyObj .arr[2].location = 'helloworld';

// 打印输出结果,进行对比;
console.log(JSON.stringify(obj)) 
// {"name":"wangfei","arr":[1,2,{"location":{"nation":["china","japan"]}}]}
console.log(JSON.stringify(copyObj)) 
// {"name":"wangfei","arr":["啊哈哈",2,{"location":"helloworld"}]}

小结: 通过以上代码发现,封装的函数可以实现对复杂数据类型的深度拷贝,是一个比较完善的方案;

如果是比较着急解决这个问题的朋友,可以先拷贝上面的函数去用,然后再看下面的部分;

- 基本数据的赋值问题

// 基本数据类型(Number,String,Boolean,Undefined,Null)的拷贝可以直接使用“=”;
    //  Number 类型
    var a  = 1;
    var b = a;
    b = 2;
    console.log(a,b); // 输出 1  2;

    // String类型;
    var a = 'hellowold';
    var b = a;
    b = 'demo';
    console.log(a,b); // 输出 hellowold demo

    //Boolean类型
    var a = false;
    var b = a;
    b = 'hello';
    console.log(a,b); // 输出 false "hello" 

   //  Undefined;
    var a = undefined;
    var b = a;
    b = false;
    console.log(a,b); //输出 undefined false

   // Null
    var a = null;
    var b = a;
    b = undefined;
    console.log(a,b); // 输出null undefined;

通过上面的代码,我们可以发现 基本数据类型可以直接通过“=” 进行赋值,两者之间不会受到影响;

- 复杂数据类型

下面会以对象,数组,函数,日期对象以及正则等为例进行分析

1 对象数据类型

    let obj = {name : 'zhangsan'};
  // 用 “=” 进行赋值
    let copyObj = obj;
    copyObj.name = 'lisi';
  // 输出结果进行对比
    console.log(obj)  // {name: "lisi"}
    console.log(copyObj) // {name: "lisi"}

2 数组

    let arr = ['a',1,'b'];
// 用 “=” 进行赋值
    let copyArr = arr;
    copyArr[0] = 'first';
 // 输出结果进行对比
    console.log(arr)  // 输出  ["first", 1, "b"]
    console.log(copyArr) // 输出   ["first", 1, "b"]

3 函数

  let fun1 = function()  {
        alert('fun1');
    }
// 用 “=” 进行赋值
    let fun2 = fun1;
    fun2 = function() {
        alert('fun2')
    }
// 输出结果进行对比
    console.log(fun1.toString())
    // function ()  {alert('fun1'); }
    console.log(fun2)
    // function ()  {alert('fun2'); }

4 日期(Date)

   var time = new Date('2019-07-12 00:00:00');
// 用 “=” 进行赋值
    var copyTime  = time;
    copyTime = new Date('2012-12-12: 00:00:00')
// 输出结果进行对比
    console.log(time) // Fri Jul 12 2019 00:00:00 GMT+0800 (中国标准时间)
    console.log(copyTime) // Wed Dec 12 2012 00:00:00 GMT+0800 (中国标准时间)

5 正则

     var rule = new RegExp('abc');
// 用 “=” 进行赋值
    var newRule = rule;
    newRule = new RegExp('hello');
// 输出结果进行对比
    console.log(rule) // /abc/
    console.log(newRule) // /hello/

小结:
1 当拷贝的数据类型为对象或者数组时,拷贝的数据发生变化的时,数据源也会发生变化;
2 当类型为Date,function,Regexp时,拷贝的数据发生变化时,数据源不会发生变化

- 通过Object.assign()对对象进行拷贝(只能实现浅拷贝)

    var obj = {name:'张三'};
// 拷贝数据
    var  copyObj = Object.assign(obj);  // 此时只改变了对象的指向;
    copyObj.name = "李四";
// 输出结果进行对比
    console.log(obj) // {name: "李四"}
    console.log(copyObj) //  {name: "李四"}

// 拷贝数据
    copyObj = Object.assign({},obj); // 浅拷贝  拷贝了对象的值;
    copyObj.name = "王五"; 
// 输出结果进行对比
    console.log(obj); // {name: "张三"}
    console.log(copyObj); // {name: "王五"}

小结: Object.assign()在实现拷贝对象时,需要传入“{}”作为参数,才能对复杂数据类型实现浅拷贝

- 通过Object.assign()对数组进行拷贝(只能实现浅拷贝)

    var arr = ['a','b','c'];
// 拷贝数据
    var copyArr = Object.assign(arr); // 此时值改变了对象的指向;
    copyArr[0] = 'change';
// 输出结果进行对比
    console.log(arr); //["change", "b", "c"]
    console.log(copyArr) // ["change", "b", "c"]

// 拷贝数据
    copyArr = Object.assign([],arr); // 浅拷贝  拷贝了数组的值;
    copyArr[0] = "change";
// 输出结果进行对比
    console.log(arr) //  ["a", "b", "c"]
    console.log(copyArr); //   ["change", "b", "c"]

小结: Object.assign()在实现拷贝对象时,需要传入“[]”作为参数,才能对复杂数据类型实现浅拷贝

注: 通过以上代码,貌似实现了对对象和数组的拷贝工作,其实是存在bug , 代码如下:

 var  obj = {
            name:'zhangsan',
            assets:{
                save:10000,
                salary:8000
            }
   }
  var copyObj = Object.assign({},obj); 
// 此时只能实现第一层数据的拷贝,更深层级的数据实现的都是改变了指向;
    copyObj.assets.save = "999";
    copyObj.name = 'lisi'
// 输出结果 进行对比
    console.log(JSON.stringify(obj))
 // {"name":"zhangsan","assets":{"save":"999","salary":8000}}
    console.log(JSON.stringify(copyObj))
 // {"name":"lisi","assets":{"save":"999","salary":8000}}

    var arr = [1,2,{data:'100'}];
// 拷贝数据
    var copyArr = Object.assign([],arr);
// 修改拷贝后的数据
    copyArr[0] = "hellowold";
    copyArr[2].data = "一百万";
//  输出结果  进行对比
    console.log(JSON.stringify(arr)); // [1,2,{"data":"一百万"}]
    console.log(JSON.stringify(copyArr)) //  ["hellowold",2,{"data":"一百万"}]

小结: 通过以上代码发现,Object.assign()方法只是实现了对复杂数据类型的第一层数据的拷贝,对于更深层次的数据只是改变了指向;

- 通过 for in 来实现拷贝
 function copyData (a) { // 只适用于只有一层数据结构的对象或数组;
        var res = a.constructor()
        console.log(res)
        for(var key in a) {
            if(a.hasOwnProperty(key)) {
                res[key] = a[key];
            }
        }
        return res;
  }

// 示例

//  1 对象数据类型
   var obj = {name:'wangfei'};
//  调用方法 进行赋值
    var copyObj = copyData(obj); 
    copyObj.name = '吴彦祖';
// 输出结果 进行对比
    console.log(obj); // {name: "wangfei"}
    console.log(copyObj) // {name: "吴彦祖"}

// 2 数组数据类型
    var arr = ['a','b','c'];
//  调用方法 进行赋值
    var copyArr = copyData(arr);
    copyArr[0] = 1000;
// 输出结果 进行对比
    console.log(arr) //  ["a", "b", "c"]
    console.log(copyArr) //  [1000, "b", "c"]
  • 注:通过上面的代码实现,貌似封装的方法可以实现复杂数据类型的拷贝工作,其实是有缺陷的,当数组结构包含两层或多层复杂数据类型时,就无法完成拷贝工作了 ,代码如下:
   var arr = [1,2,{name:'zhangwuji'}];
//  拷贝数据
    var copyArr = copyData(arr);
    copyArr[0] = '变化';
    copyArr[2].name = '张无忌';
// 输出结果  进行对比
    console.log(JSON.stringify(arr)) // [1,2,{"name":"张无忌"}]
    console.log(JSON.stringify(copyArr)) // ["变化",2,{"name":"张无忌"}]
- 通过JSON.stringify()和JSON.parse()实现对象的拷贝(深拷贝,不完善)
// 将JSON.stringify() 和 JSON.parse()封装成一个方法;
 function deepCopy(obj) {
        return JSON.parse(JSON.stringify(obj));
    }

    var arr = [1,2,{name:'zhangwuji'}];
// 拷贝数据
    var res = deepCopy(arr); // 此时木问题;
    res[0] = '哈哈';
    res[2].name = "张无忌";
// 输出结果,进行对比
    console.log(JSON.stringify(arr)) // [1,2,{"name":"zhangwuji"}]
    console.log(JSON.stringify(res)) // [1,2,{"name":"zhangwuji"}]

    var  obj = {
            name:'zhangsan',
            assets:{
                save:10000,
                salary:8000
            }
       }
//   拷贝数据
    var copyObj = deepCopy(obj); // 此时 木问题 !!!!
    copyObj.name = '王菲';
    copyObj.assets.save = "一千万";
// 输出结果,进行对比
     console.log(JSON.stringify(obj))  
// {"name":"zhangsan","assets":{"save":10000,"salary":8000}}
     console.log(JSON.stringify(copyObj)) 
 //  {"name":"王菲","assets":{"save":"一千万","salary":8000}}
  • 注: .上述的方法会忽略值为function以及undefined的字段,而且对date类型的支持也不太友好; 代码如下:
 var obj = {
         name:'wangfei',
         fun:function(){
                console.log('fun')
          },
          data:undefined,
   }
// 拷贝数据
    var copyObj = deepCopy(obj);
// 输出结果,进行对比
    console.log(obj)
    console.log(JSON.stringify(obj)) //   {"name":"wangfei"}
    console.log(copyObj.fun) // undefined
    console.log(copyObj.data) // undefined
    console.log(JSON.stringify(copyObj))  // {"name":"wangfei"}

总结: 通过以上分析,可以看出 ,这几种方法都存在一定的局限性;还是在文章开头封装的方法,能够比较完善的实现js中数据(简单数据类型&复杂数据类型)的拷贝工作;

你可能感兴趣的:(js 对象深拷贝&浅拷贝)