複製陣列後修改新的陣列,結果舊的陣列也跟著被改動了,怎麼會這樣???
一開始遇到這樣的問題,還以為是什麼Bug在作亂
但冷靜思考過後,發現這是很基本但容易忽略的問題(但又不好解決)
那到底要如何複製陣列,又不影響原陣列呢?
首先回想一下,基本型別(Primitive Type)跟物件(Object)兩者有什麼差異呢
基本型別是Number,String,Boolean
(若是其他程式語言還會細分Int,Char,Long......等等)
而物件則包含自己透過function或用{}建立的物件(Object)
其中也包含我們今天的主角:陣列(Array)
而基本型別和物件最大的差異就是傳值的方法
基本型別是傳值(call by value)
而物件則是傳參考(call by reference)
這時要先來介紹一下記憶體的部分
記憶體通常會分成幾個部分
最常聽到的就是stack、heap、static等等
可以想成不同等級的房子,stack是最基本的、heap是比較高級的
所以會依照用途使用到不同的記憶體位置
基本型別會將值儲存在stack的位置
一個變數就給一個stack的位置
所以當第二個變數要參考到第一個變數時,就會直接找到stack的位置取走裡面的值
比如說
var a = 10;
var b = a; //此時b取走a的值,所以b等於10
console.log(b); //10
b = 100; //在這裡我去修改b的值
console.log(a); //a和b不相關,所以b的改動不會影響到a,故a=10
console.log(b); //b=100
這就是所謂的傳值(call by value)
但物件會將資料儲存在heap位置
而將heap記憶體位置的地址儲存在stack裡
因此對下列程式碼來講
var arr = [0,1,2];
JS會將陣列資料[0,1,2]儲存在heap裡
而將記憶體位置儲存在stack區裡的變數arr
又或者像下列程式碼
var obj = new Object();
JS會在heap建立一個新的Object(但裡面是空的沒有值)
而將Object的記憶體位置的地址儲存在stack區裡的變數obj
因此
var arr1 = [0,1,2];
var arr2 = arr1;
console.log(arr2); //[0,1,2]
第二行的程式碼實際上只是將arr1所儲存在stack裡的記憶體位置傳給arr2
這就是所謂的傳參(call by reference)
而等號 '=' 並不是讓兩個陣列相等,而是將arr1記憶體位置指派給arr2,這也是為什麼等號會是'指派'運算子
然而問題來了
var arr1 = [0,1,2];
var arr2 = arr1;
arr[1] = 9;
這樣會發生什麼事呢?
答案會是
console.log(arr1) //[0,9,2]
console.log(arr2); //[0,9,2]
原因是arr1和arr2這兩個變數只是儲存著陣列的記憶體位置
而這兩個變數所儲存的記憶體位置是相同的
代表兩個變數會參考到同一個陣列
所以arr2更改陣列的值時,也會影響到arr1所參考到的陣列(畢竟就同一個嘛)
那要如何複製陣列,修改時才不會影響到彼此呢?
首先先來討論Javascript陣列中的淺拷貝(Shallow Copy)與深拷貝(Deep Copy)
淺拷貝在複製時會參考到同一個物件,也就是複製記憶體位置
深拷貝則是會使用獨立的heap記憶體位置,複製成一個完全沒相關聯的新陣列
所以要深拷貝陣列,才能達到不影響彼此的情況
目前大多數複製陣列的方法都是淺拷貝
舉凡像是slice,map...等等
不過淺拷貝適用於一維陣列
條件是陣列裡的元素不能是物件
只要符合條件,使用淺拷貝也能達成新舊陣列不互相影響的狀況
舉例來說
var arr1 = [0,1,2];
var arr2 = arr1.slice(0);
arr2[1] = 9;
console.log(arr1); //[0,1,2]
console.log(arr2); //[0,9,2]
但如果陣列是多維的話,淺拷貝就不適用了
雖然在網路上看到很多深拷貝的方法
像是ES6的新函式Object.assign
或是jquery的$extend
但實際使用上卻都失敗
最後是使用JSON.stringify把陣列轉成字串
再用JSON.parse把字串轉成新的物件
畢竟換成字串之後就是新的東西啦,就沒有參考記憶體位置的狀況了
但這個方法有些缺點
最主要的就是物件格式要能轉成JSON
如果是function就沒辦法了
而且多了一道處理手續,多少會影響到效能
當然還有土法煉鋼,自己一個一個慢慢加,但陣列一大就很刺激了
可惜目前如果不使用第三方函式庫的話,好像也只有JSON轉換能做到深拷貝
而其他還有像lodash函式庫聽說也能達到,不過沒有實際去測試
如果以後有找到更好用的方法會再更新上來~~~~