Muterbara och Oföränderliga i TypeScript

Muterbara och Oföränderliga i TypeScript

Den här artikeln förklarar begreppen muterbarhet och oföränderlighet i TypeScript.

YouTube Video

Muterbara och Oföränderliga i TypeScript

Vad är Muterbart?

Muterbart betyder att ett värde kan ändras. Referenstyper som objekt och arrayer är typiska exempel på muterbara datastrukturer.

Exempel på ett Muterbart Objekt

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 }

I denna kod ändras age-egenskapen hos person-objektet från 25 till 26. Eftersom objekt passeras som referens, ändras innehållet vid minnesadressen som lagras i variabeln person.

Exempel på en Muterbar Array

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

I denna kod används push-metoden för att lägga till ett nytt element 4 i den ursprungliga arrayen. Detta ändrar den ursprungliga arrayen, vilket gör det till en muterbar operation.

Exempel i en Funktion

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]

När muterbara operationer utförs i en funktion, ändras också den ursprungliga arrayen.

Vad är Oföränderligt?

Oföränderligt betyder att ett värde inte kan ändras. Primitiva typer är i grunden oföränderliga.

Exempel på en Oföränderlig Primitiv Typ

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

Att försöka ändra den första bokstaven i strängen str till H misslyckas eftersom strängar är oföränderliga.

Exempel i en Funktion

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)

Eftersom siffror är oföränderliga påverkar inte operationer inuti en funktion den ursprungliga variabeln.

Oföränderliga Operationer på Arrayer

Arrayer är muterbara, men genom att skapa en ny array istället för att ändra den ursprungliga, kan oföränderliga operationer uppnås.

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)

Här används spridningssyntaxen (...) för att skapa en ny array newNumbers. Eftersom den ursprungliga arrayen numbers förblir oförändrad, är detta en oföränderlig operation.

Fördelar med att använda oföränderliga datastrukturer

Förbättrad förutsägbarhet

Oföränderliga data ändras inte, vilket gör oväntade ändringar mindre sannolika och minskar risken för buggar.

Kompatibilitet med bibliotek som bygger på oföränderlighet

Bibliotek som React och Redux är ofta designade utifrån oföränderliga data, vilket gör tillståndshantering enklare när de används på rätt sätt.

Gör objekt oföränderliga med Object.freeze

Object.freeze kan användas för att förhindra ändringar i ett objekt.

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 }

Men Object.freeze utför endast en ytlig frysning, vilket innebär att egenskaper hos nästlade objekt förblir ändringsbara.

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)

För att skapa ett helt oföränderligt objekt krävs en djupfrysning.

 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)

Sammanfattning

  • Ändringsbara data kan modifieras och inkluderar objekt och arrayer.
  • Oföränderliga data kan inte modifieras och inkluderar primitiva typer som strängar och nummer.
  • Med hjälp av spridningssyntax eller metoder som map kan operationer på oföränderliga data utföras.
  • Object.freeze och deepFreeze kan användas för att förhindra ändringar i objekt.
  • Att använda oföränderliga data hjälper till att skriva mer förutsägbar och mindre felbenägen kod.

Att anta en oföränderlig design förbättrar kodsäkerhet och läsbarhet, så dra full nytta av det!

Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.

YouTube Video