深拷贝与浅拷贝理解及拷贝方法

  在说深拷贝、浅拷贝之前,首先先说一下js里存在的两种数据类型:基本数据类型和引用数据类型。

- 基本数据类型有:boolean Null Undefined Number String Symbol
- 引用类型有:Object(包含 Array、Function、RegExp、Date等等)

区别:基本数据类型名值都在栈内存中,而引用数据类型的名存在栈内存中,值存在堆内存中,但是栈内存会提供一个引用地址指向堆内存中的值。

  因为JS不允许直接访问堆内存,也不能直接对堆内存进行操作,所以,引用类型的值是按引用访问的。


  • 深拷贝 & 浅拷贝

  深拷贝、浅拷贝是专门针对于引用数据类型的一种概念,因为对于引用数据类型的名、值存在不同的内存空间当中,所以当发生拷贝行为的时候,系统会开辟一块新的栈内存空间,存放的是被复制的变量在栈内存中的值,即堆内存中的值的引用地址。

  即新的变量复制得到的是被复制对象的引用地址,当变量发生改变值的操作时,指向该对象的地址的其他变量的值也会随之变化,此为浅拷贝。

  当然,我们有时候并不希望这样的场景发生,所以这时候就需要用到深拷贝的方法去进行拷贝。

堆、栈内存.jpg


  • 深拷贝 & 浅拷贝常用方法
    • 浅拷贝方法:
      ( = 号只是引用,并没有发生拷贝行为,在内存中并没有开辟新的空间!!!)

      1. slice() // 操作对象:数组
      
          let a = [1,2,3,"4",{name:"lme"}];
          let b = a.slice(0);
          console.log("a: ",a); // [1,2,3,"4",{name:"lme"}]
          console.log("b: ",b); // [1,2,3,"4",{name:"lme"}]
      
          b[4].name = "dsg";
          console.log("修改b,打印a: ",a); // [1,2,3,"4",{name:"dsg"}]
      
      2. concat() // 操作对象:数组
      
          let a = [1,2,3,"4",{name:"lme"}];
          let b = a.concat();
          console.log("a: ",a); // [1,2,3,"4",{name:"lme"}]
          console.log("b: ",b); // [1,2,3,"4",{name:"lme"}]
      
          b[4].name = "cool";
          console.log("修改b,打印a: ",a); // [1,2,3,"4",{name:"cool"}]
      
      3. Array.from() // 操作对象:数组
      
          let a = [1,2,3,"4",{name:"lme"}];
          let b = Array.from(a);
          console.log("a: ",a); // [1,2,3,"4",{name:"lme"}]
          console.log("b: ",b); // [1,2,3,"4",{name:"lme"}]
      
          b[4].name = "cool";
          console.log("修改b,打印a: ",a); // [1,2,3,"4",{name:"cool"}]
          
      4. ...操作符 // 操作对象:数组 && 对象
      
          let a = [1,2,3,"4",{name:"lme"}];
          let b = [...a];
          console.log("a: ",a); // [1,2,3,"4",{name:"lme"}]
          console.log("b: ",b); // [1,2,3,"4",{name:"lme"}]
      
          b[4].name = "cool";
          console.log("修改b,打印a: ",a); // [1,2,3,"4",{name:"cool"}]
          
          let c = {age:18,sex:1,child:{name:"小明",age:2,sex:1}};
          let d = {...c};
          console.log("c: ", c); // {age:18,sex:1,child:{name: "小明",age: 2,sex: 1}}
          console.log("d: ", d); // {age:18,sex:1,child:{name: "小明",age: 2,sex: 1}}
      
          d.child.name = "小红";
          console.log("修改d,打印c: ", c); // {age:18,sex:1,child:{name: "小红",age: 2,sex: 1}}
      
      5. Object.assign() // 操作对象:对象
      
          let c = {age:18,sex:1,child:{name:"小明",age:2,sex:1}};
          let d = Object.assign({},c);
          console.log("c: ", c); // {age:18,sex:1,child:{name: "小明",age: 2,sex: 1}}
          console.log("d: ", d); // {age:18,sex:1,child:{name: "小明",age: 2,sex: 1}}
      
          d.child.name = "小红";
          console.log("修改d,打印c: ", c); // {age:18,sex:1,child:{name: "小红",age: 2,sex: 1}}
      
    • 深拷贝方法:

      1. // 当所拷贝的数组或对象的一维元素或属性都是 基本数据类型 的时候  
         // 可以使用以上浅拷贝的方法去达成一次深拷贝的结果。
         slice()、concat、Array.from()、...操作符、Object.assign()
         // 注意:当且仅当 数组或对象的一维元素或者属性全部都是 基本数据类型 的时候
         // 拷贝结果才是深拷贝,否则为浅拷贝!!!
      
      2. JSON.parse(JSON.stringify(obj))  // 序列化 -> 反序列化
         // 将数组或对象序列化转成字符串,然后再反序列化转成对象,由于字符串是String类型,
         // 属于基本类型,所以会切断与源对象引用地址的联系而形成一个新的对象。
      
         // 该方法可以对多维对象进行深拷贝,但是需要注意的是,在序列化时,某些特定的值或者类型将会丢失
      
         如下:
          1. Date时间对象 
      
             // 如果obj里的某一条属性的值为时间对象,则序列化之后再反序列化得到的结果,
             // 时间将只是字符串的形式存在,而不是时间对象。
      
          2. RegExp、Error对象   
      
             // 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象。
      
          3. 函数、undefined 
      
             // 如果obj里有函数、undefined,则序列化的结果会把函数或undefine丢失。
      
          4. NaN 
             // 如果obj里有NaN,则序列化的结果会变成null
      
          5. constructor 
             // 如果obj中的某一条属性的值是由构造函数生成的对象,则使用
             // JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor构造函数。
             // 比如:
                  function Person(name) {
                      this.name = name;
                  }
          
                  let lme = new Person('lme');
          
                  let a = {
                      name: 'a',
                      data: lme,
                  };
      
                  let b = JSON.parse(JSON.stringify(a));
      
                  console.log("a",a); // 构造函数:Person类型
                  console.log("b",b); // 构造函数:无
      
                  b.name = "gyj";
                  b.data.name = "gyj is lme's wife"
      
                  console.log("a1: ",a) // 原始a对象没有被修改
                  console.log("b1: ",b) // b对象被修改
      
                // 感兴趣的同学可以打印一下看看呦~
      
          PS: 该方法简单粗暴,适用于简单的引用类型数据,使用前请三思~
      
      3. 自行编写函数进行递归遍历复制,实现深拷贝。
      
         // 先根据要拷贝的变量的类型声明一个新的变量,用for in遍历要拷贝的变量,然后判断如果当前
         // 循环中的key(属性)的值是一个对象,那么便递归,对该属性继续进行遍历拷贝,否则直接将
         // 该属性的值赋值给新的对象的属性。
      
         // 如果需要将原型链上继承过来的属性过滤掉的话,可使用hasOwnProperty(),该方法会判断传
         // 入的参数是否在对象上,如果是原型链上的属性或方法,则会返回false。
      
         例:
            function deepCopy(obj) {
              let newObj = obj.constructor === Array ? [] : {} 
      
              for (let key in obj) { // Array实际上也是一个对象,即也是引用数据类型
                typeof obj[key] === 'object' ? newObj[key] = deepCopy(obj[key]) : newObj[key] = obj[key]
              }
      
              return newObj
            }
      
      
      



(限于本人技术有限,本文如有表述不当的地方,欢迎赐教~)
(转载到其他平台请包含本文的链接或说明出处~)

你可能感兴趣的:(深拷贝与浅拷贝理解及拷贝方法)