JS中的深拷贝和浅拷贝

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/baidu_36065997/article/details/80309991

相关知识点

1.javascript变量包含两种不同数据类型的值:基本类型和引用类型

  • 基本类型值指的是简单的数据段,包括es6里面新增的一共是有6种,具体如下:number、string、boolean、null、undefined、symbol。

  • 引用类型值指那些可能由多个值构成的对象,只有一种如下:object。
    在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值。

2.javascript的变量的存储方式:栈(stack)和堆(heap)

  • 栈:自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址

  • 堆:动态分配的内存,大小不定,也不会自动释放。里面存放引用类型的值。
    这里写图片描述
    基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。
    引用类型的值是保存在内存中的对象。JavaScript 不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。 在操作对象时, 实际上是在操作对象的引用而不是实际的对象。

基本类型与引用类型最大的区别实际就是 传值与传址 的区别

值传递:基本类型采用的是值传递。
地址传递:引用类型则是地址传递,将存放在栈内存中的地址赋值给接收的变量。

浅拷贝

浅拷贝
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。

  • 基本数据类型Number(赋值操作)
let a=1;
let b=a;
b           //1
b=2;
b          //2
a          //1
  • 数组
let arr1 = [1,2,3];
let arr2 = arr1;
arr2         //[1,2,3]
arr2.push(4);
arr2         //[1,2,3,4]
arr1         //[1,2,3,4]

这里写图片描述
首先栈内存arr1会指向堆内存里的数组,栈内存的arr1保存的是数组的引用,也就相当于内存地址,arr2=arr1,会把arr1的引用赋给arr2,所以arr2也有了数组的引用,此时arr1和arr2指向的是同一个数组,因此一个数组的改变会影响另一个数组的值。

  • 对象
let obj1={count:1,name:'grace',age:1};
let obj2 = obj1;
obj2        //{count:1,name:'grace',age:1}
obj2.count=2;
obj1        //{count:2,name:'grace',age:1}
obj2        //{count:2,name:'grace',age:1}

综上所述,如果是基本数据类型,直接进行赋值操作,这样就相当于在栈内存中重新开辟了一个新的空间把值传递过去;如果是引用类型的值传递,进行的就是浅拷贝,浅拷贝赋值的只是对象的引用,如上述obj2=obj1,实际上传递的只是obj1的内存地址,所以obj2和obj1指向的是同一个内存地址,所以这个内存地址中值的改变对obj1和obj2都有影响。

深拷贝

深拷贝不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制到新对象上,所以对一个对象的修改并不会影响另一个对象。

  • 数组

    • 法一:for循环

      let arr1 = [1,2,3];
      let arr2 = copyArr(arr1);
      function copyArr(arr){
          let res=[];
          for(let i=0,length=arr.length;i<length;i++){
              res.push(arr[i]);
          }
          return res;
      }
    • 法二: slice
      利用数组自身的方法,slice、concat方法在运行后会返回新的数组

      let arr1 = [1,2,3];
      let arr2 = arr1.slice(0);
    • 法三: concat

      let arr1 = [1,2,3];
      let arr2 = arr1.concat();
    • 法四:扩展运算符

      let arr1 = [1,2,3];
      let [...arr2] = arr1;
    • 法五:Array.from
      如果参数是一个真正的数组,Array.from会返回一个一模一样的新数组
    let arr1 = [1,2,3];
    let arr2 = Array.from(arr1);
  • 对象

    • 法一:for循环

      let obj1={count:1,name:'grace',age:1};
      let obj2 = copyObj(obj){
      let res = {};
      for(let key in obj){
        res[key]=obj[key];
      }
      return res;
      }
    • 法二:利用JSON

    let obj1={count:1,name:'grace',age:1};
    let obj2 = JSON.parse(JSON.stringify(obj1));

//使用JSON比较简单,但是JSON的深拷贝方式会忽略函数对象和原型对象(有待考证)

  • 法三:扩展运算符
    let obj1={count:1,name:'grace',age:1};
    let {...obj2} = obj1;

合成版,可以实现数组和对象的深拷贝

function deepCopy(obj){
        let result = Array.isArray(obj)?[]:{};  
        if(obj && typeof obj === 'object'){ 
            for(let key in obj){
                if(obj.hasOwnProperty(key)){
                    if(obj[key]&&typeof obj[key]==='object'){
                        result[key]=deepCopy(obj[key]);
                    }else{
                        result[key]=obj[key];
                    }
                }
            }
        }
        return result;
    }

注意:ES6新增了Object.assign() 方法
第一个参数是目标对象,之后还可以跟一个或多个源对象。它会遍历一个或多个源对象可枚举的自有键并把它们复制到目标对象,最后返回目标对象
assign是使用=操作符来赋值,

Object.assign() 只是一级属性复制,比浅拷贝多深拷贝了一层而已。用的时候,还是要注意这个问题的

猜你喜欢

转载自blog.csdn.net/baidu_36065997/article/details/80309991