javascript簡單實現深淺拷貝(附帶詳細講解)

  • 2019 年 10 月 3 日
  • 筆記

深淺拷貝知識在我們的日常開發中還算是用的比較多,但是之前的狀態一直都是只曾聽聞,未曾使用(其實用了只是自己沒有意識到),所以今天來跟大家聊一聊js的深淺拷貝;

  首先我們來了解一下javascript的數據類型,在ES5版本的js中我們的javascript一共有6種數據類型,分別是:

  Number(數值型)、String(字元串)、Boolean(布爾型)、Object(對象,object和array都屬於Object類型)、null、undefined

  我們日常使用的javascript深淺拷貝主要是面向Object引用類型進行拷貝;

  

  我們知道了js的深淺拷貝面對的執行操作對象,然後我們再來看一下深淺拷貝的概念:

     拷貝顧名思義就是複製,記憶體中一共分為棧記憶體和堆記憶體兩大區域,所謂深淺拷貝主要是對javascript引用類型數據進行拷貝一份,淺拷貝就是引用類型數據相互賦值之後,例obj1=obj2;如果後面的操作中修改obj1或者obj2,這個時候數據是會進行相應的變化的,因為在記憶體中引用類型數據是存儲在堆記憶體中,堆記憶體中存放的是引用類型的值,同時會有一個指針地址指向棧記憶體,兩個引用類型數據地址一樣,如果其中一個發生變化另外一個都會有影響;而深拷貝則不會,深拷貝是會在堆記憶體中重新開闢一塊空間進行存放;

 

    基本類型複製:

var a = 1;  var b = a;//複製  console.log(b)//1  a = 2;//改變a的值  console.log(b)//1  console.log(a) //2

 

  因為a,b都是屬於基本類型,基本類型的複製是不會影響對方的,因為基本類型是每一次創建變數都會在棧記憶體中開闢一塊記憶體,用來存放值,所以基本類型進行複製是不會對另外一個變數有影響的;

 

    引用類型複製:

      引用類型的複製我們分為數組的複製和對象的複製兩個方面來進行講解:

      js的淺拷貝:

var arr1 = ['red','green'];  var arr2 = arr1;//複製  console.log(arr2)//['red','green'];  arr1.push('black') ;//改變color1的值  console.log(arr2)//['red','green','black']  console.log(arr1) //["red", "green", "black"]

    上面的案例是javascript數組的淺拷貝,通過上面的知識我們可以看知道數組是引用類型數據,引用類型數據複製是會進行相互影響的,我們看到arr1.push(‘black’)添加了一個新的子項,因為上面var arr2=arr1這行程式碼是將兩個引用類型數據的地址指針指向了同一塊堆記憶體區域,所以不管是arr1還是arr2修改,任何一個一個改動兩個數組都是會互相產生影響的;上面的那種直接賦值方式的複製就是我們常說的引用類型的淺拷貝;

     關於深拷貝很多同學都誤以為js的原生方法concat、slice是屬於深拷貝,其實不是的;js的原生方法concat、slice都是僅適用於一維數組,一旦到了二維數組或者多維數組中就會出現問題,就出現拷貝的不夠徹底導致還是會發生數據的相互牽引問題;

        slice:

var arr1 = ['red','green'];  var arr2 = arr1.slice(0);//複製  console.log(arr2)//['red','green'];  arr1.push('black') ;//改變color1的值  console.log(arr2)//["red", "green"]  console.log(arr1)//["red", "green", "black"]

      js原生的方法slice會返回一個新的數組,上述程式碼乍一看會以為是深拷貝,因為arr2和arr1相互複製和牽引,而當arr1調用了push方法添加了新數組子項的時候,arr2沒有發生變化;是的,這是符合深拷貝的特性,但是拷貝的不夠徹底,所以還不能算是真正意義上的深拷貝,所以slice只能被稱為淺拷貝;slice方法只適用於一維數組的拷貝,在二維數組中就會破綻百出;

      下面我們再來看一下二維數組的例子:

var arr1=[1,2,3,['1','2','3']];  var arr2=arr1.slice(0);   arr1[3][0]=0;   console.log(arr1);//[1,2,3,['0','2','3']]   console.log(arr2);//[1,2,3,['0','2','3']]

       上述程式碼是一個二維數組,當我們在arr1[3][0]裡面進行更改arr1的值的時候,我們發現arr1、arr2兩個數組的值都發生了變化;所以事實證明slice不是深拷貝;

 

      concat:

var arr1 = ['red','green'];  var arr2 = arr1.concat();//複製  console.log(arr2)//['red','green'];  arr1.push('black') ;//改變color1的值  console.log(arr2)//["red", "green"]  console.log(arr1)//["red", "green", "black"]

 

 

var arr1=[1,2,3,['1','2','3']];  var arr2=arr1.concat();   arr1[3][0]=0;   console.log(arr1);//[1,2,3,['0','2','3']]   console.log(arr2);//[1,2,3,['0','2','3']]

      

       concat方法在一維數組中是不會影響源數組的數據的,而在二維數組中concat的表現和slice是一樣的;

 

 

      js的深拷貝:

      js數組中實現深拷貝的方法都多種,比如JSON.parse(JSON.stringify())和遞歸以及JQuery庫的extend方法(只是extend方法需要依賴JQuery庫,所以我們盡量的使用原生的方式來實現)都是可以實現數組和對象的深拷貝的;

var arr1 = ['red','green'];  var arr2 = JSON.parse(JSON.stringify(arr1));//複製  console.log(arr2)//['red','green'];  arr1.push('black') ;//改變color1的值  console.log(arr2)//["red", "green"]  console.log(arr1)//["red", "green", "black"]

     上述程式碼中我們可以清晰的看到JSON.parse(JSON.stringify())是真正意義上實現了深拷貝;

    

         遞歸實現深拷貝:

      

function deepClone(obj){      //判斷參數是不是一個對象      let objClone = obj instanceof Object?[]:{};      if(obj && typeof obj==="object"){          for(key in obj){              if(obj.hasOwnProperty(key)){                  //判斷ojb子元素是否為對象,如果是,遞歸複製                  if(obj[key]&&typeof obj[key] ==="object"){                      objClone[key] = deepClone(obj[key]);                  }else{                      //如果不是,簡單複製                      objClone[key] = obj[key];                  }              }          }      }      return objClone;  }    var a ={      x:1,      y:2  };      b=deepClone(a);  a.x=3  console.log(a);  console.log(b);

 

  輸出效果如下:

  

 

 

 

    總結:

        1:深拷貝只是從源數據中拷貝一份出來進行操作,而不是改變源數據;改變源數據的那是淺拷貝;

        2:原生js方法slice、concat都不是真正意義上的深拷貝,都僅只適用於一維數組,拷貝的屬性不夠徹底;

        3:實現js深拷貝我們可以通過JSON.parse(JSON.stringify())、遞歸以及JQuery庫的extend方法來實現;