深拷贝和浅拷贝

126 阅读5分钟

深拷贝和浅拷贝

一、深拷贝和浅拷贝的概念

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一内存。但深拷贝会另外创造一个一模一样的对象,新旧对象不共享内存,修改新对象不会改到原对象。

浅拷贝:只是拷贝数据的内存地址,而不是在内存中重新创建一个一模一样的对象/数组。

深拷贝:在内存中开辟一个新的存储空间,完完全全的拷贝一个一模一样的对象/数组。

不论是number、string、boolean还是object、array都会被存储在内存中。内存又被分为栈内存和堆内存。 基本数据类型(如:string、number、boolean、null、undefined)会被直接存储到栈内存中。像数组、对象等由多种基本数据类型组成的复杂数据类型,他们的实体内容会被存储到堆内存中。栈内存只会存储他们在堆内存中的一串地址。

二、深拷贝、浅拷贝和赋值的区别

1、赋值是两个对象一起指向一个对象,无论是谁改变了对象里面的内容都会互相影响。

2、浅拷贝和深拷贝都是将拷贝的内容放入新的对象中去,区别在于浅拷贝只会新增一个外层对象来放要拷贝对象的所有内容,所以如果要拷贝的对象里面的属性是对象则拷贝的是对象的引用,而深拷贝则是被拷贝对象包含多少对象,深拷贝就会对应生成多少对象来--对应往里装被拷贝对象里的内容,所有的对象都是独立全新的拷贝一份。

简单来说:

  • 赋值操作两个变量指向同一个对象,两者互相影响。
  • 浅拷贝新生成一个对象,对象里面的属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址。
  • 深拷贝会新生成所有对象(对象里面层层嵌套对象),对象里面的属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),会新生成一个对象将内容拷贝进去。

三、浅拷贝的实现方式

1.Object.assgin()

Object.assgin()方法可以把任意多个源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是Object.assgin()进行的是浅拷贝,拷贝的是对象的引用,而不是对象本身。

  var obj = {
     a:{
        a:'hello',
        b:18
     }
  }
  var objData = Object.assgin({},obj);
  objData.a.a = 'changed';
  
  console.log(obj.a.a);              //changed

如果对象里面的属性不是对象,则进行的是深拷贝。

  var obj = {
      a:10,
      b:20,
      c:30
  }
  var objData = Object.assgin({},obj);
  objData.b = 100;
  console.log(obj);              //{a:10,b:20,c:30}
  console.log(objData);          //{a:10,b:100,c:30}
2、Array.prototype.concat()
  let arr = [1,3,{username:'小赵'}];
  let arr2 = arr.concat();
  arr2[2].username = '张三';
  console.log(arr);          //[1,3,{username:'张三'}]
  //修改新对象会影响原对象
3、Array.prototype.slice()
  let arr = [1,3,{username:'小赵'}];
  let arr2 = arr.slice();
  arr2[2].username = '张三';
  console.log(arr);          //[1,3,{username:'张三'}]
  //修改新对象同样会影响原对象

关于Array的slice和concat方法补充说明:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中元素的一个数组。

原数组的元素会按照下述规则拷贝:

  1. 如果该元素是个对象引用(不是实际的对象),slice会拷贝这个对象引用到新的数组里面。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中这个元素也会发生改变。
  2. 对于字符串、数字和布尔值来说,slice会拷贝这些值到新的数组里面。在别的数组里修改这些字符串、数组或是布尔值,不会影响另一个数组。

例:

  let arr = [1,3,{username:'小赵'}];
  let arr2 = arr.slice();
  arr2[1] = 10
  console.log(arr,arr2);          //[1,3,{username:'小赵'}]        [10,3,{username:'小赵'}]

四、深拷贝实现方式

1、JSON.parse(JSON.stringify())

用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。可以实现数组或对象深拷贝,但不能处理函数。

   var obj = {
      a:{
         b:10
      }
   }
   var objData = JSON.parse(JSON.stringify(obj));
   objData.a.b = 20;
   console.log(obj);            //{a:{b:10}}
   console.log(objData);        //{a:{b:20}}
   console.log(obj === objData);              //false
   console.log(obj.a === objData.a);          //false
JSON.stringify的坑
  • 当对象中有时间类型的元素的时候--时间类型会变成字符串类型数据

       const obj = {
          date:new Date()
       }
       typeof obj.date === 'object'        //true
       const objCopy = JSON.parse(JSON.stringify(obj));
       typeof objCopy.date === 'string'      //true
    
  • 当对象中有undefined类型或function类型的数据时--undefined和function会直接丢失

       const obj = {
          undef:undefined,
          fun:()=>{
             console.log('000')
          }
       }
       console.log(obj);            //{undef:undefined,fun:f}
       const objCopy = JSON.parse(JSON.stringify(obj))
       console.log(objCopy)         //{}
    
  • 当对象中有NaN、Infinity和-Infinity这三种值的时候--会变成null

    1.7976931348623157E+10308是浮点数的最大上限,显示为Infinity

    -1.7976931348623157E+10308是浮点数的最小下限,显示为-Infinity

       const obj = {
           nan:NaN,
           infinityMax:1.7976931348623157E+10308, 
           infinityMin:-1.7976931348623157E+10308,
       }
       console.log(obj);      //{nan:NaN,infinityMax:Infinity,infinityMin:-Infinity}
       const objCopy = JSON.parse(JSON.stringify(obj));
       console.log(objCopy);  //{nan:null,infinityMax:null,infinityMin:null}
    
  • 当对象循环引用的时候--报错

      const obj = {
         objChild:null
      }
      obj.objChild = obj;
      const objCopy = JSON.parse(JSON.stringify(obj));
      console.log(objCopy);
    

1658845178929.jpg

2、递归拷贝

递归方法实现深度克隆原理:遍历对象、数组知道里面都是基本数据类型,然后再去复制,就是深度拷贝。

  var obj = {
     a:{
       name:'小赵'
     },
     b:2,
     arr:[1,2]
  }
  var objData = {}
  //参数:初始值,完成值
  function deepClone(initalObj,finalObj) {
    var obj = finalObj || {};
    for(var i in initalObj) {
      //判断是都引用类型,object,Array的typeof检测都是object
      if(typeof initalObj[i] === 'object') {
         //递归前,判断是对象还是数字,初始化
         obj[i] = (initalObj[i].constructor === Array) ? [] : {};
         //递归自己
         arguments.callee(initalObj[i],obj[i]);
      } else {
         //基础类型值  直接复制
         obj[i] = inital[i]
      }
    }
    return obj;
  }
  
  deepClone(obj,objData);
  console.log(objData);                  //{a:{name:'小赵'},b:2,arr:[1,2]}
3、lodash

该函数库提供了_.cloneDeep用来深拷贝

  var _ = require('lodash');
  var obj = {
     a:1,
     b:{
       f:{
         g:1
       }
     },
     c:[1,2,3]
  }
  var objData = _.cloneDeep(obj);
  console.log(obj.b.f === objData.b.f);                              //false
4、Object.create()

直接使用var newObj = Object.create(oldObj)可以达到深拷贝的效果。

  function deepClone(initalObj,finalObj){
     var obj = finalObj || {};
     for(var i in initalObj){
       var prop = initalObj[i];
       if(prop === obj){
         continue;
       }
       if(typeof prop === 'object') {
         obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
       } else {
         obj[i] = prop;
       }
     }
     return obj;
  }