在 TypeScript 中的可變與不可變
本篇文章將解釋在 TypeScript 中的可變與不可變概念。
YouTube Video
在 TypeScript 中的可變與不可變
什麼是可變 (Mutable)?
可變 (Mutable) 表示一個值是 可以被改變的。例如物件和陣列等引用類型是典型的可變數據結構。
可變物件的範例
1type Person = { name: string; age: number };
2
3// Mutable Example: Object
4let person: Person = { name: "Alice", age: 25 };
5person.age = 26;
6console.log(person); // { name: "Alice", age: 26 }
在此程式碼中,person 物件的 age 屬性從 25 被改為 26。由於物件是 以引用 (reference) 傳遞的,因此 person 變數中記憶體位址的內容已被修改。
可變陣列的範例
1// Mutable Example: Array
2let numbers: number[] = [1, 2, 3];
3numbers.push(4);
4console.log(numbers); // [1, 2, 3, 4]
在此程式碼中,使用 push 方法向原始陣列添加了一個新的元素 4。此操作修改了 原始陣列,使其成為一個可變操作。
函式中的範例
1// Mutable Example: Function
2function append(arr: number[], value: number): void {
3 arr.push(value); // Modify the original array
4 console.log(arr);
5}
6
7let nums: number[] = [1, 2, 3];
8append(nums, 4);
9console.log(nums); // [1, 2, 3, 4]
在函式中執行可變操作時,原始陣列也會被修改。
什麼是不可變 (Immutable)?
不可變 (Immutable) 表示一個值是 無法被改變的。基礎類型 (Primitive types) 本質上是不可變的。
不可變基礎類型範例
1// Immutable Example: String
2let str: string = "hello";
3str[0] = "H"; // Error: Index assignment is not allowed
4console.log(str); // "hello"
嘗試將字串 str 的第一個字元改為 H 會失敗,因為字串是 不可變的。
函式中的範例
1// Immutable Example: Function
2function increment(num: number): number {
3 num++; // This modifies only the local copy of num
4 return num;
5}
6
7let number: number = 10;
8console.log(increment(number)); // 11
9console.log(number); // 10 (original number remains unchanged)
由於數字是不可變的,因此函式中的操作不會影響原始變數。
陣列的不可變操作
陣列是可變的,但 通過創建一個新的陣列而非修改原始陣列,可以實現不可變操作。
1// Create an array of numbers
2let numbers: number[] = [1, 2, 3];
3
4// Immutable Example: Creating a new array
5let newNumbers: number[] = [...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 陣列保持不變,這是一個不可變操作。
使用不可變數據結構的好處
提高可預測性
不可變數據不會改變,減少了意外修改的可能性,從而降低了出現錯誤的幾率。
與基於不可變性的庫的兼容性
像 React 和 Redux 這樣的庫通常基於不可變數據設計,適當使用時可以簡化狀態管理。
使用 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: Readonly<{ profile: { name: string } }> = Object.freeze({
3 profile: { name: "Bob" }
4});
5
6// Attempt to modify a nested property (this works because Object.freeze() is shallow)
7user.profile.name = "Charlie"; // No TypeScript error, but still mutable
8
9console.log(user.profile.name); // "Charlie" (nested object is still mutable)
要創建一個完全不可變的對象,需要進行深層凍結。
1// Function to deeply freeze an object, making all nested objects immutable
2function deepFreeze<T>(obj: T): Readonly<T> {
3 Object.freeze(obj);
4 Object.getOwnPropertyNames(obj).forEach(prop => {
5 const value = (obj as any)[prop];
6 if (typeof value === "object" && value !== null) {
7 deepFreeze(value);
8 }
9 });
10 return obj;
11}
12
13// Create a deeply frozen object
14const user = deepFreeze({
15 profile: { name: "Bob" }
16});
17
18// Attempt to modify a nested property
19// (this will now throw an error in strict mode)
20user.profile.name = "Charlie"; // No TypeScript error, but modification is not allowed at runtime
21
22console.log(user.profile.name); // "Bob" (unchanged due to deep freeze)
總結
- 可變數據是可修改的,包括對象和陣列。
- 不可變數據是不可修改的,包括字符串和數字這類的基本類型。
- 使用展開語法或像
map這樣的方法,可以執行不可變數據操作。 Object.freeze和deepFreeze可用於防止對對象的修改。- 使用不可變數據有助於編寫更可預測且更少出錯的代碼。
採用不可變設計可以提高代碼的安全性和可讀性,請充分利用它!
您可以在我們的 YouTube 頻道上使用 Visual Studio Code 來跟隨上述文章一起學習。 請也查看我們的 YouTube 頻道。