在 JavaScript 中的可變性與不可變性

在 JavaScript 中的可變性與不可變性

此文章說明了 JavaScript 中的可變性與不可變性概念。

YouTube Video

在 JavaScript 中的可變性與不可變性

什麼是可變 (Mutable)?

可變 (Mutable) 的意思是值可以被修改。物件 (Object)陣列 (Array) 作為 引用型別 (reference types),是可變資料結構的典型範例。

可變物件的範例

1let person = { name: "Alice", age: 25 };
2person.age = 26;
3console.log(person); // { name: "Alice", age: 26 }

在此程式碼中,person 物件的 age 屬性從 25 修改為 26。由於物件是透過 參考 (reference) 傳遞的,person 變數儲存的 記憶體位址 (memory address) 中的內容被修改了。

可變陣列的範例

1let numbers = [1, 2, 3];
2numbers.push(4);
3console.log(numbers); // [1, 2, 3, 4]

在此程式碼中,使用了 push 方法將新元素 4 添加到原陣列中。此操作 修改了原始陣列,因而是一種可變的操作。

在函式中的範例

 1// Function to append a value to an array
 2function append(arr, value) {
 3    arr.push(value); // Modify the original array
 4    console.log(arr);
 5}
 6
 7let numbers = [1, 2, 3];
 8append(numbers, 4);
 9
10console.log(numbers); // [1, 2, 3, 4] (original array is modified)

當在函式中執行可變操作時,原始陣列也會被修改。

什麼是不可變 (Immutable)?

不可變 (Immutable) 的意思是值無法被修改。原始型別 (Primitive types) 本質上是不可變的。

不可變原始型別的範例

1let str = "hello";
2str[0] = "H";
3console.log(str); // "hello"

嘗試將字串 str 的第一個字元更改為 H 失敗,因為字串是 不可變 (immutable) 的。

在函式中的範例

 1// Function to increment a number
 2function increment(num) {
 3    num++; // This modifies only the local copy of num
 4    console.log(num);
 5}
 6
 7let number = 10;
 8increment(number);
 9
10console.log(number); // 10 (original number remains unchanged)

由於數字是不可變的,函式中的操作不會影響到原始變數。

對陣列的不可變操作

雖然陣列是可變的,但透過 創建新陣列而非修改原陣列 可實現不可變操作。

1// Create an array of numbers
2let numbers = [1, 2, 3];
3
4// Create a new array by spreading the original and adding a new element
5let newNumbers = [...numbers, 4];
6
7console.log(numbers); // [1, 2, 3] (original array is unchanged)
8console.log(newNumbers); // [1, 2, 3, 4] (new array with an added element)

在這裡,展開語法(...)用於創建一個新的陣列 newNumbers。由於原始的 numbers 陣列沒有被修改,因此這是一個不可變的操作。

使用不可變資料結構的好處

改進的可預測性

由於不可變的資料無法更改,意外的修改不太可能發生,從而減少了程式錯誤的風險。

與基於不可變性的庫的相容性

ReactRedux 這樣的庫通常以不可變資料為設計核心,正確使用時可以讓狀態管理更簡單。

透過 Object.freeze 使物件不可變

Object.freeze 可用於防止對物件的修改。

1// Create a frozen object (properties cannot be modified)
2const person = Object.freeze({ name: "Alice", age: 25 });
3
4// Attempt to modify a property (ignored in non-strict mode, error in strict mode)
5person.age = 26;
6
7console.log(person); // { name: "Alice", age: 25 }

然而,Object.freeze 進行的是淺層凍結,這意味著嵌套物件的屬性依然是可變的。

1// Create a frozen object with a nested object
2const user = Object.freeze({ profile: { name: "Bob" } });
3
4// Attempt to modify a nested property (this works because Object.freeze() is shallow)
5user.profile.name = "Charlie";
6
7console.log(user.profile.name); // "Charlie" (nested object is still mutable)

要創建完全不可變的物件,需要進行深層凍結

 1// Function to deeply freeze an object, making all nested objects immutable
 2function deepFreeze(obj) {
 3  Object.keys(obj).forEach(key => {
 4    if (typeof obj[key] === "object" && obj[key] !== null) {
 5      deepFreeze(obj[key]); // Recursively freeze nested objects
 6    }
 7  });
 8  return Object.freeze(obj); // Freeze the top-level object
 9}
10
11// Create a deeply frozen object
12const user = deepFreeze({ profile: { name: "Bob" } });
13
14// Attempt to modify a nested property (ignored)
15user.profile.name = "Charlie";
16
17console.log(user.profile.name); // "Bob" (unchanged due to deep freeze)

總結

  • 可變 資料可以被修改,包括物件和陣列。
  • 不可變 資料無法被修改,包括字串和數字等基本類型。
  • 使用展開語法或 map 可以實現不可變的資料操作。
  • Object.freezedeepFreeze 可用於防止對物件的修改。
  • 使用不可變資料可以讓程式碼更具可預測性且更少出錯。

不可變的設計提高了程式碼的安全性和可讀性,因此要好好利用它!

您可以在我們的 YouTube 頻道上使用 Visual Studio Code 來跟隨上述文章一起學習。 請也查看我們的 YouTube 頻道。

YouTube Video