Mutable ו-Immutable ב-TypeScript

Mutable ו-Immutable ב-TypeScript

מאמר זה מסביר את המושגים Mutable ו-Immutable ב-TypeScript.

YouTube Video

Mutable ו-Immutable ב-TypeScript

מה זה Mutable?

Mutable משמעו שערך יכול להשתנות. סוגי הפניה כמו אובייקטים ומערכים הם דוגמאות שכיחות למבני נתונים 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 }

בקוד זה, מאפיין ה-age של אובייקט ה-person משתנה מ-25 ל-26. מכיוון שאובייקטים מוחזרים בהפניה, התוכן שבכתובת הזיכרון שמורה במשתנה person משתנה.

דוגמה למערך Mutable

1// Mutable Example: Array
2let numbers: number[] = [1, 2, 3];
3numbers.push(4);
4console.log(numbers); // [1, 2, 3, 4]

בקוד זה, שיטת push משמשת להוספת רכיב חדש 4 למערך המקורי. זה משנה את המערך המקורי, ובכך הופך את הפעולה ל-Mutable.

דוגמה בתוך פונקציה

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]

כאשר מבצעים פעולות Mutable בתוך פונקציה, גם המערך המקורי משתנה.

מה זה Immutable?

Immutable משמעו שערך לא ניתן לשינוי. סוגי נתונים פרימיטיביים הם למעשה Immutable.

דוגמה לסוג פרימיטיבי Immutable

1// Immutable Example: String
2let str: string = "hello";
3str[0] = "H"; // Error: Index assignment is not allowed
4console.log(str); // "hello"

ניסיון לשנות את התו הראשון של המחרוזת str ל-H נכשל מכיוון שמחרוזות הן Immutable.

דוגמה בתוך פונקציה

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)

מכיוון שמספרים הם Immutable, פעולות בתוך פונקציה אינן משפיעות על המשתנה המקורי.

פעולות Immutable על מערכים

מערכים הם Mutable, אך על ידי יצירת מערך חדש במקום לשנות את המקורי, ניתן לבצע פעולות Immutable.

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 כדי למנוע שינויים באובייקטים.
  • שימוש בנתונים בלתי-ניתנים לשינוי מסייע לכתיבת קוד שניתן לחיזוי ועם פחות שגיאות.

אימוץ עיצוב בלתי-ניתן לשינוי משפר את הבטיחות והקריאות של הקוד, לכן נצלו זאת במלואו!

תוכלו לעקוב אחר המאמר שלמעלה באמצעות Visual Studio Code בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.

YouTube Video