初學者指南:深入了解 JavaScript 陣列的參考(Reference)
更新日期: 2024 年 10 月 20 日
在 JavaScript 中,陣列是一種常用的資料結構,可以存儲多個值。
與原始資料類型(如數字、字串等)不同,陣列是一種引用資料類型(Reference Type)。
這意味著,當你將一個陣列賦值給另一個變數或傳遞到函數時,它們並不會創建一個新的陣列,而是引用同一個記憶體位置。
這對於初學者來說可能有些難以理解,因此本文將詳細介紹 JavaScript 陣列的參考行為,並通過範例來說明如何正確操作陣列。
什麼是引用類型?
在 JavaScript 中,引用類型是指那些存儲在記憶體中的資料,其變數存儲的是資料的「指向」,而不是資料本身。
陣列和物件都是引用類型,而像數字、字串等基本類型則是原始類型(Primitive Type)。
當你處理原始類型時,變數存儲的是實際的值,例如:
let a = 10;
let b = a;
b = 20;
console.log(a); // 輸出:10
console.log(b); // 輸出:20
在這裡,變數 a
和 b
各自存儲自己的數值。即使我們修改了 b
,a
仍然保持不變。
但是,當我們處理陣列等引用類型時,情況就有所不同。
陣列的引用行為
當你將一個陣列賦值給另一個變數時,兩個變數都引用同一個陣列。
這意味著,當你修改其中一個變數中的陣列時,另一個變數中的陣列也會反映這些變更。
範例:
let arr1 = [1, 2, 3];
let arr2 = arr1;
arr2.push(4);
console.log(arr1); // 輸出:[1, 2, 3, 4]
console.log(arr2); // 輸出:[1, 2, 3, 4]
說明:
- 在這裡,我們將
arr1
賦值給arr2
,這使得arr1
和arr2
引用同一個陣列。 - 當我們使用
arr2.push(4)
向陣列添加元素時,arr1
也發生了變化,因為兩者指向的是同一個記憶體地址。
這就是引用類型的特性。無論你修改哪個變數,變更都會影響所有指向同一記憶體地址的變數。
如何避免引用引發的問題
有時候,你可能不希望兩個變數共享同一個陣列,而是希望創建一個新的副本。
在這種情況下,你需要將陣列複製到一個新變數,而不是僅僅賦值。
使用 slice()
複製陣列
slice()
方法可以用來創建陣列的淺拷貝。
它返回一個新的陣列,並不會影響原陣列。
範例:
let arr1 = [1, 2, 3];
let arr2 = arr1.slice();
arr2.push(4);
console.log(arr1); // 輸出:[1, 2, 3]
console.log(arr2); // 輸出:[1, 2, 3, 4]
說明:
- 這裡我們使用
arr1.slice()
創建了一個新陣列arr2
。 - 當我們修改
arr2
時,arr1
沒有受到影響,因為arr2
是arr1
的拷貝,而不是引用。
使用展開運算子(...
)複製陣列
另一種常用的複製陣列的方式,是使用 ES6 提供的展開運算子(...
)。
範例:
let arr1 = [1, 2, 3];
let arr2 = [...arr1];
arr2.push(4);
console.log(arr1); // 輸出:[1, 2, 3]
console.log(arr2); // 輸出:[1, 2, 3, 4]
說明:
- 使用展開運算子,我們可以快速創建一個新的陣列。
- 和
slice()
一樣,arr2
是arr1
的一個淺拷貝,修改arr2
不會影響arr1
。
淺拷貝與深拷貝
在前面的範例中,我們介紹了如何使用 slice()
和展開運算子來創建陣列的淺拷貝。
但是,淺拷貝只會複製陣列的第一層元素。
對於多維陣列或陣列中的物件來說,這並不會創建完全獨立的副本。
淺拷貝的問題
當陣列中包含物件或其他陣列時,淺拷貝只會複製最外層的引用,內層的引用仍然指向原始物件。
範例:
let arr1 = [[1, 2], [3, 4]];
let arr2 = arr1.slice();
arr2[0].push(5);
console.log(arr1); // 輸出:[[1, 2, 5], [3, 4]]
console.log(arr2); // 輸出:[[1, 2, 5], [3, 4]]
說明:
- 雖然
arr2
是arr1
的拷貝,但內部的子陣列仍然是引用。因此,對arr2[0]
的修改影響了arr1
。
深拷貝
為了創建完全獨立的副本,我們需要進行深拷貝。
深拷貝會遞迴地複製陣列中的所有層次,確保每一層都是獨立的。
JavaScript 中可以使用 JSON.parse(JSON.stringify())
來實現深拷貝,但這種方法不適用於處理函數或特殊物件。
範例:
let arr1 = [[1, 2], [3, 4]];
let arr2 = JSON.parse(JSON.stringify(arr1));
arr2[0].push(5);
console.log(arr1); // 輸出:[[1, 2], [3, 4]]
console.log(arr2); // 輸出:[[1, 2, 5], [3, 4]]
陣列參考的實際應用場景
理解陣列的引用行為非常重要,因為它可以幫助你避免無意中修改資料,特別是在處理複雜資料結構或需要傳遞資料的時候。
以下是幾個常見的應用場景:
函數中修改陣列
當陣列作為參數傳遞到函數中時,該函數可以修改原始陣列,因為傳遞的是陣列的引用。
範例:
function modifyArray(arr) {
arr.push(4);
}
let myArray = [1, 2, 3];
modifyArray(myArray);
console.log(myArray); // 輸出:[1, 2, 3, 4]
防止無意修改
如果你不希望函數修改原始陣列,可以使用 slice()
或展開運算子創建一個副本。
範例:
function modifyArray(arr) {
let newArr = arr.slice(); // 或者 [...arr]
newArr.push(4);
return newArr;
}
let myArray = [1, 2, 3];
let newArray = modifyArray(myArray);
console.log(myArray); // 輸出:[1, 2, 3]
console.log(newArray); // 輸出:[1, 2, 3, 4]
結語
在 JavaScript 中,理解陣列的引用行為至關重要,特別是在操作大量資料時。
通過掌握如何正確使用引用類型,你可以避免不必要的錯誤,同時也能更有效地處理資料。
當你需要創建陣列副本時,根據具體需求選擇淺拷貝還是深拷貝的方式,這能幫助你編寫更可靠的程式碼。