Unveränderlichkeit ist ein Kernprinzip in der funktionalen Programmierung und hat auch objektorientierten Programmen viel zu bieten. Ein veränderliches Objekt ist ein Objekt, dessen Zustand nach seiner Erstellung geändert werden kann. Ein unveränderliches Objekt ist ein Objekt, dessen Zustand nach seiner Erstellung nicht geändert werden kann.
Was ist ein Beispiel für ein unveränderliches Objekt in JavaScript?
In JavaScript sind einige eingebaute Typen (Zahlen, Strings) unveränderlich, aber benutzerdefinierte Objekte sind im Allgemeinen veränderlich.
Einige eingebaute unveränderliche JavaScript-Objekte sind Math
, Date
.
Hier sind ein paar Möglichkeiten, um Unveränderlichkeit bei einfachen JavaScript-Objekten hinzuzufügen/zu simulieren.
Objektkonstante Eigenschaften
Durch die Kombination von writable: false
und configurable: false
können Sie im Wesentlichen eine Konstante (kann nicht geändert, neu definiert oder gelöscht werden) als Objekteigenschaft erstellen, wie zum Beispiel:
let myObject = {};
Object.defineProperty(myObject, 'number', {
value: 42,
writable: false,
configurable: false,
});
console.log(myObject.number); // 42
myObject.number = 43;
console.log(myObject.number); // 42
Erweiterungen verhindern
Wenn Sie verhindern möchten, dass einem Objekt neue Eigenschaften hinzugefügt werden, die restlichen Eigenschaften des Objekts jedoch unangetastet lassen möchten, rufen Sie Object.preventExtensions(...)
auf:
var myObject = {
a: 2,
};
Object.preventExtensions(myObject);
myObject.b = 3;
myObject.b; // undefined
Im Nicht-Strict-Modus schlägt die Erstellung von b
stillschweigend fehl. Im Strict-Modus wird ein TypeError
ausgelöst.
Versiegeln (Seal)
Object.seal()
erstellt ein "versiegeltes" Objekt, was bedeutet, dass es ein vorhandenes Objekt nimmt und im Wesentlichen Object.preventExtensions()
darauf aufruft, aber auch alle seine vorhandenen Eigenschaften als configurable: false
markiert.
Sie können also nicht nur keine weiteren Eigenschaften hinzufügen, sondern auch keine vorhandenen Eigenschaften neu konfigurieren oder löschen (obwohl Sie deren Werte noch ändern können).
Einfrieren (Freeze)
Object.freeze()
erstellt ein eingefrorenes Objekt, was bedeutet, dass es ein vorhandenes Objekt nimmt und im Wesentlichen Object.seal()
darauf aufruft, aber es markiert auch alle "Datenzugriffs"-Eigenschaften als writable: false
, so dass ihre Werte nicht geändert werden können.
Dieser Ansatz ist die höchste Stufe der Unveränderlichkeit, die Sie für ein Objekt selbst erreichen können, da er jegliche Änderungen am Objekt oder an einer seiner direkten Eigenschaften verhindert (obwohl, wie oben erwähnt, der Inhalt aller referenzierten anderen Objekte unberührt bleibt).
var immutable = Object.freeze({});
Das Einfrieren eines Objekts erlaubt es nicht, neue Eigenschaften zu einem Objekt hinzuzufügen und verhindert das Entfernen oder Ändern bestehender Eigenschaften. Object.freeze()
bewahrt die Aufzählbarkeit, Konfigurierbarkeit, Schreibbarkeit und den Prototyp des Objekts. Es gibt das übergebene Objekt zurück und erstellt keine eingefrorene Kopie.
Was sind die Vor- und Nachteile der Unveränderlichkeit?
Vorteile
- Einfachere Änderungsdetektion - Die Objektgleichheit kann performant und einfach durch referentielle Gleichheit bestimmt werden. Dies ist nützlich für den Vergleich von Objektunterschieden in React und Redux.
- Programme mit unveränderlichen Objekten sind einfacher zu durchdenken, da Sie sich keine Gedanken darüber machen müssen, wie sich ein Objekt im Laufe der Zeit entwickeln könnte.
- Verteidigungskopien sind nicht mehr notwendig, wenn unveränderliche Objekte von oder an Funktionen zurückgegeben oder übergeben werden, da keine Möglichkeit besteht, dass ein unveränderliches Objekt dadurch geändert wird.
- Einfaches Teilen über Referenzen - Eine Kopie eines Objekts ist genauso gut wie eine andere, so dass Sie Objekte zwischenspeichern oder dasselbe Objekt mehrmals wiederverwenden können.
- Thread-sicher - Unveränderliche Objekte können in einer Multithread-Umgebung sicher zwischen Threads verwendet werden, da keine Gefahr besteht, dass sie in anderen gleichzeitig laufenden Threads geändert werden.
- Durch die Verwendung von Bibliotheken wie ImmutableJS werden Objekte mittels struktureller Freigabe modifiziert, und es wird weniger Speicher für mehrere Objekte mit ähnlichen Strukturen benötigt.
Nachteile
- Naive Implementierungen von unveränderlichen Datenstrukturen und ihren Operationen können zu extrem schlechter Leistung führen, da jedes Mal neue Objekte erstellt werden. Es wird empfohlen, Bibliotheken für effiziente unveränderliche Datenstrukturen und Operationen zu verwenden, die auf struktureller Freigabe basieren.
- Die Zuweisung (und Freigabe) vieler kleiner Objekte anstatt der Modifikation bestehender kann sich auf die Leistung auswirken. Die Komplexität des Allocators oder des Garbage Collectors hängt in der Regel von der Anzahl der Objekte im Heap ab.
- Zyklische Datenstrukturen wie Graphen sind schwierig zu erstellen. Wenn Sie zwei Objekte haben, die nach der Initialisierung nicht geändert werden können, wie können Sie sie dazu bringen, aufeinander zu zeigen?
Wie können Sie Unveränderlichkeit in Ihrem eigenen Code erreichen?
Die Alternative ist die Verwendung von const
-Deklarationen in Kombination mit den oben genannten Techniken zur Erstellung. Zum "Mutieren" von Objekten verwenden Sie den Spread-Operator, Object.assign
, Array.concat()
usw., um neue Objekte zu erstellen, anstatt das ursprüngliche Objekt zu mutieren.
Beispiele:
// Array-Beispiel
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // [1, 2, 3, 4]
// Objekt-Beispiel
const human = Object.freeze({ race: 'human' });
const john = { ...human, name: 'John' }; // {race: "human", name: "John"}
const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}