深拷貝與淺拷貝理解及拷貝方法

??在說深拷貝、淺拷貝之前,首先先說一下js里存在的兩種數(shù)據(jù)類型:基本數(shù)據(jù)類型和引用數(shù)據(jù)類型。

- 基本數(shù)據(jù)類型有:boolean Null Undefined Number String Symbol
- 引用類型有:Object(包含 Array、Function、RegExp、Date等等)

區(qū)別:基本數(shù)據(jù)類型名值都在棧內(nèi)存中,而引用數(shù)據(jù)類型的名存在棧內(nèi)存中,值存在堆內(nèi)存中,但是棧內(nèi)存會提供一個引用地址指向堆內(nèi)存中的值。

??因為JS不允許直接訪問堆內(nèi)存,也不能直接對堆內(nèi)存進行操作,所以,引用類型的值是按引用訪問的。


  • 深拷貝 & 淺拷貝

??深拷貝、淺拷貝是專門針對于引用數(shù)據(jù)類型的一種概念,因為對于引用數(shù)據(jù)類型的名、值存在不同的內(nèi)存空間當中,所以當發(fā)生拷貝行為的時候,系統(tǒng)會開辟一塊新的棧內(nèi)存空間,存放的是被復(fù)制的變量在棧內(nèi)存中的值,即堆內(nèi)存中的值的引用地址。

??即新的變量復(fù)制得到的是被復(fù)制對象的引用地址,當變量發(fā)生改變值的操作時,指向該對象的地址的其他變量的值也會隨之變化,此為淺拷貝。

??當然,我們有時候并不希望這樣的場景發(fā)生,所以這時候就需要用到深拷貝的方法去進行拷貝。

堆、棧內(nèi)存.jpg


  • 深拷貝 & 淺拷貝常用方法
    • 淺拷貝方法:
      ( = 號只是引用,并沒有發(fā)生拷貝行為,在內(nèi)存中并沒有開辟新的空間!?。。?br>
      1. slice() // 操作對象:數(shù)組
      
          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() // 操作對象:數(shù)組
      
          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() // 操作對象:數(shù)組
      
          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. ...操作符 // 操作對象:數(shù)組 && 對象
      
          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. // 當所拷貝的數(shù)組或?qū)ο蟮囊痪S元素或?qū)傩远际?基本數(shù)據(jù)類型 的時候  
         // 可以使用以上淺拷貝的方法去達成一次深拷貝的結(jié)果。
         slice()、concat、Array.from()、...操作符、Object.assign()
         // 注意:當且僅當 數(shù)組或?qū)ο蟮囊痪S元素或者屬性全部都是 基本數(shù)據(jù)類型 的時候
         // 拷貝結(jié)果才是深拷貝,否則為淺拷貝?。?!
      
      2. JSON.parse(JSON.stringify(obj))  // 序列化 -> 反序列化
         // 將數(shù)組或?qū)ο笮蛄谢D(zhuǎn)成字符串,然后再反序列化轉(zhuǎn)成對象,由于字符串是String類型,
         // 屬于基本類型,所以會切斷與源對象引用地址的聯(lián)系而形成一個新的對象。
      
         // 該方法可以對多維對象進行深拷貝,但是需要注意的是,在序列化時,某些特定的值或者類型將會丟失
      
         如下:
          1. Date時間對象 
      
             // 如果obj里的某一條屬性的值為時間對象,則序列化之后再反序列化得到的結(jié)果,
             // 時間將只是字符串的形式存在,而不是時間對象。
      
          2. RegExp、Error對象   
      
             // 如果obj里有RegExp、Error對象,則序列化的結(jié)果將只得到空對象。
      
          3. 函數(shù)、undefined 
      
             // 如果obj里有函數(shù)、undefined,則序列化的結(jié)果會把函數(shù)或undefine丟失。
      
          4. NaN 
             // 如果obj里有NaN,則序列化的結(jié)果會變成null
      
          5. constructor 
             // 如果obj中的某一條屬性的值是由構(gòu)造函數(shù)生成的對象,則使用
             // JSON.parse(JSON.stringify(obj))深拷貝后,會丟棄對象的constructor構(gòu)造函數(shù)。
             // 比如:
                  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); // 構(gòu)造函數(shù):Person類型
                  console.log("b",b); // 構(gòu)造函數(shù):無
      
                  b.name = "gyj";
                  b.data.name = "gyj is lme's wife"
      
                  console.log("a1: ",a) // 原始a對象沒有被修改
                  console.log("b1: ",b) // b對象被修改
      
                // 感興趣的同學(xué)可以打印一下看看呦~
      
          PS: 該方法簡單粗暴,適用于簡單的引用類型數(shù)據(jù),使用前請三思~
      
      3. 自行編寫函數(shù)進行遞歸遍歷復(fù)制,實現(xiàn)深拷貝。
      
         // 先根據(jù)要拷貝的變量的類型聲明一個新的變量,用for in遍歷要拷貝的變量,然后判斷如果當前
         // 循環(huán)中的key(屬性)的值是一個對象,那么便遞歸,對該屬性繼續(xù)進行遍歷拷貝,否則直接將
         // 該屬性的值賦值給新的對象的屬性。
      
         // 如果需要將原型鏈上繼承過來的屬性過濾掉的話,可使用hasOwnProperty(),該方法會判斷傳
         // 入的參數(shù)是否在對象上,如果是原型鏈上的屬性或方法,則會返回false。
      
         例:
            function deepCopy(obj) {
              let newObj = obj.constructor === Array ? [] : {} 
      
              for (let key in obj) { // Array實際上也是一個對象,即也是引用數(shù)據(jù)類型
                typeof obj[key] === 'object' ? newObj[key] = deepCopy(obj[key]) : newObj[key] = obj[key]
              }
      
              return newObj
            }
      
      
      



(限于本人技術(shù)有限,本文如有表述不當?shù)牡胤剑瑲g迎賜教~)
(轉(zhuǎn)載到其他平臺請包含本文的簡書鏈接或說明出處~)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容