初學者指南:深入了解 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

在這裡,變數 ab 各自存儲自己的數值。即使我們修改了 ba 仍然保持不變。

但是,當我們處理陣列等引用類型時,情況就有所不同。


陣列的引用行為

當你將一個陣列賦值給另一個變數時,兩個變數都引用同一個陣列。

這意味著,當你修改其中一個變數中的陣列時,另一個變數中的陣列也會反映這些變更。

範例:

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,這使得 arr1arr2 引用同一個陣列。
  • 當我們使用 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 沒有受到影響,因為 arr2arr1 的拷貝,而不是引用。

使用展開運算子(...)複製陣列

另一種常用的複製陣列的方式,是使用 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() 一樣,arr2arr1 的一個淺拷貝,修改 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]]

說明:

  • 雖然 arr2arr1 的拷貝,但內部的子陣列仍然是引用。因此,對 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 中,理解陣列的引用行為至關重要,特別是在操作大量資料時。

通過掌握如何正確使用引用類型,你可以避免不必要的錯誤,同時也能更有效地處理資料。

當你需要創建陣列副本時,根據具體需求選擇淺拷貝還是深拷貝的方式,這能幫助你編寫更可靠的程式碼。

Similar Posts