1. 程式人生 > >javascript簡單實現深淺拷貝

javascript簡單實現深淺拷貝

深淺拷貝知識在我們的日常開發中還算是用的比較多,但是之前的狀態一直都是隻曾聽聞,未曾使用(其實用了只是自己沒有意識到),所以今天來跟大家聊一聊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=arr這行程式碼是將兩個引用型別資料的地址指標指向了同一塊堆記憶體區域,所以不管是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方法來實現;

     

&n