慎用JS中的slice()、concat()和assign()方法來複制陣列

隔壁張小二發表於2018-07-29

一、原陣列裡的資料不包含引用型別

let arr1 = [1 , 2 , 3 , "hello" , "world"];  //原陣列
複製程式碼

1、使用 slice() 方法

拷貝陣列:

let arr2 = arr1.slice(0);
console.log(arr2);   //列印新陣列
[1 , 2 , 3 , "hello" , "world"];  //新陣列
複製程式碼

修改經過 slice() 拷貝過的新陣列:

arr2[3] = "Hello";
console.log(arr1);  //列印舊陣列
[1 , 2 , 3 , "hello" , "world"]   
console.log(arr2);   //列印新陣列
[1 , 2 , 3 , "Hello" , "world"]
複製程式碼

結論:使用 slice() 方法拷貝陣列,然後修改新陣列,不會影響到舊陣列的值。

2、使用 concat() 方法

拷貝陣列:

let arr3 = [].conat(arr1);
console.log(arr3);   //列印新陣列
[1 , 2 , 3 , "hello" , "world"];  //新陣列
複製程式碼

修改經過 concat() 拷貝過的新陣列

arr3[3] = "Hello";
console.log(arr1);  //列印舊陣列
[1 , 2 , 3 , "hello" , "world"]   
console.log(arr3);   //列印新陣列
[1 , 2 , 3 , "Hello" , "world"]
複製程式碼

結論:使用 concat() 方法拷貝陣列,然後修改新陣列,不會影響到舊陣列的值。

3、使用 assign() 方法,es6中的物件方法,注意瀏覽器的相容性

拷貝陣列:

let arr4 = Object.assign({} , arr1);
console.log(arr4);   //列印新陣列
[1 , 2 , 3 , "hello" , "world"];  //新陣列
複製程式碼

修改經過 assign() 拷貝過的新陣列

arr4[3] = "Hello";
console.log(arr1);  //列印舊陣列
[1 , 2 , 3 , "hello" , "world"]   
console.log(arr4);   //列印新陣列
[1 , 2 , 3 , "Hello" , "world"]
複製程式碼

結論:使用 assign() 方法拷貝陣列,然後修改新陣列,不會影響到舊陣列的值。

4、使用簡單的陣列賦值語法

拷貝陣列:

let arr5 = arr1;
console.log(arr5);   //列印新陣列
[1 , 2 , 3 , "hello" , "world"];  //新陣列
複製程式碼

修改經過簡單賦值過的新陣列

arr5[3] = "Hello";
console.log(arr1);  //列印舊陣列
[1 , 2 , 3 , "Hello" , "world"]   
console.log(arr5);   //列印新陣列
[1 , 2 , 3 , "Hello" , "world"]
複製程式碼

結論:使用陣列簡單賦值方法拷貝陣列,然後修改新陣列,會影響到舊陣列的值。

原因:這種簡單賦值的方法屬於陣列的淺拷貝,陣列arr1和陣列arr5共用同一塊記憶體,其中一個陣列改變,另一個陣列也會跟著改變。

二、原陣列裡的資料包含引用型別

let arr1 = [1 , 2 , 3 , {"name" : "張小二"} , {"sex" : "male"}];  //原陣列
複製程式碼

1、使用 slice() 方法

拷貝陣列:

let arr2 = arr1.slice(0);
console.log(arr2);   //列印新陣列
[1 , 2 , 3 , {"name" : "張小二"} , {"sex" : "male"}];  //新陣列
複製程式碼

修改經過 slice() 拷貝過的新陣列:

arr2[3].name = "隔壁張小二";
console.log(arr1);  //列印舊陣列
[1 , 2 , 3 , {"name" : "隔壁張小二"} , {"sex" : "male"}]   
console.log(arr2);   //列印新陣列
[1 , 2 , 3 , {"name" : "隔壁張小二"} , {"sex" : "male"}]
複製程式碼
結論:使用 slice() 方法拷貝陣列,然後修改新陣列,會改變舊陣列的值。

2、使用 concat() 方法

拷貝陣列:

let arr3 = [].conat(arr1);
console.log(arr3);   //列印新陣列
[1 , 2 , 3 , {"name" : "張小二"} , {"sex" : "male"}];  //新陣列
複製程式碼

修改經過 concat() 拷貝過的新陣列

arr3[3].name = "隔壁張小二";
console.log(arr1);  //列印舊陣列
[1 , 2 , 3 , {"name" : "隔壁張小二"} , {"sex" : "male"}]   
console.log(arr3);   //列印新陣列
[1 , 2 , 3 , {"name" : "隔壁張小二"} , {"sex" : "male"}]
複製程式碼
結論:使用 concat() 方法拷貝陣列,然後修改新陣列,會改變舊陣列的值。

3、使用 assign() 方法

拷貝陣列:

let arr4 = Object.assign({} , arr1);
console.log(arr4);   //列印新陣列
[1 , 2 , 3 , {"name" : "張小二"} , {"sex" : "male"}];  //新陣列
複製程式碼

修改經過 assign() 拷貝過的新陣列

arr4[3].name = "隔壁張小二";
console.log(arr1);  //列印舊陣列
[1 , 2 , 3 , {"name" : "隔壁張小二"} , {"sex" : "male"}]   
console.log(arr4);   //列印新陣列
[1 , 2 , 3 , {"name" : "隔壁張小二"} , {"sex" : "male"}]
複製程式碼
結論:使用 assign() 方法拷貝陣列,然後修改新陣列,會改變舊陣列的值。

三、原因分析

1、陣列的淺拷貝

(1)陣列的直接賦值屬於陣列的淺拷貝,JS儲存物件都是存記憶體地址的,所以淺拷貝會導致新陣列和舊陣列共用同一塊記憶體地址,其中一個陣列變化,另一個陣列也會相應的變化。

(2)陣列內部不含有引用型別,使用slice() 、concat() 和 assign() 方法都屬於陣列的深拷貝,一個陣列變化,另一個陣列不受影響。

(3)陣列內部含有引用型別,使用slice() 、concat() 和 assign() 方法,非引用型別的值屬於深拷貝,引入型別的值屬於淺拷貝,一個陣列變化,另一個也會相應的變化。

四、解決辦法(含有引入型別的陣列)

方法一:遞迴

let cloneObj = function(obj){
    let str, newobj = obj.constructor === Array ? [] : {};
    if(typeof obj !== 'object'){
        return;
    } else if(window.JSON){
        str = JSON.stringify(obj), //系列化物件
        newobj = JSON.parse(str); //還原
    } else {
        for(var i in obj){
            newobj[i] = typeof obj[i] === 'object' ? 
            cloneObj(obj[i]) : obj[i]; 
        }
    }
    return newobj;
};

let newArr = cloneObj(oldArr);

複製程式碼

方法二:通過JSON解析解決

let newArr = JSON.parse(JSON.stringify(oldArr));
複製程式碼
注意:這種方法拷貝後的陣列會丟失原陣列中定義的方法和陣列原型中定義的方法。

相關文章