Einer der grundlegenden Unterschiede zwischen Objekten und Grundelementen besteht darin, dass Objekte „per Referenz“ gespeichert und kopiert werden, während Grundwerte (Strings, Zahlen, Boolesche Werte usw.) immer „als Gesamtwert“ kopiert werden.
Das ist leicht zu verstehen, wenn wir ein wenig hinter die Kulissen schauen und sehen, was passiert, wenn wir einen Wert kopieren.
Beginnen wir mit einem Grundelement, beispielsweise einer Zeichenfolge.
Hier haben wir eine Kopie der message
phrase
:
let message = "Hallo!"; let Phrase = Nachricht;
Als Ergebnis haben wir zwei unabhängige Variablen, von denen jede die Zeichenfolge "Hello!"
speichert. .
Ein ziemlich offensichtliches Ergebnis, oder?
Objekte sind nicht so.
Eine einem Objekt zugewiesene Variable speichert nicht das Objekt selbst, sondern seine „Adresse im Speicher“ – mit anderen Worten „eine Referenz“ darauf.
Schauen wir uns ein Beispiel für eine solche Variable an:
let user = { Name: „John“ };
Und so wird es tatsächlich im Speicher gespeichert:
Das Objekt wird irgendwo im Speicher gespeichert (rechts im Bild), während die user
(links) einen „Verweis“ darauf hat.
Wir können uns eine Objektvariable wie user
wie ein Blatt Papier vorstellen, auf dem die Adresse des Objekts steht.
Wenn wir Aktionen mit dem Objekt ausführen, z. B. eine Eigenschaft user.name
übernehmen, schaut sich die JavaScript-Engine an, was sich an dieser Adresse befindet, und führt die Operation am tatsächlichen Objekt aus.
Hier erfahren Sie, warum es wichtig ist.
Wenn eine Objektvariable kopiert wird, wird die Referenz kopiert, aber das Objekt selbst wird nicht dupliziert.
Zum Beispiel:
let user = { name: "John" }; let admin = user; // Referenz kopieren
Jetzt haben wir zwei Variablen, die jeweils einen Verweis auf dasselbe Objekt speichern:
Wie Sie sehen, gibt es immer noch ein Objekt, aber jetzt mit zwei Variablen, die darauf verweisen.
Wir können jede Variable verwenden, um auf das Objekt zuzugreifen und seinen Inhalt zu ändern:
let user = { name: 'John' }; let admin = user; admin.name = 'Pete'; // geändert durch die „admin“-Referenz Alert(Benutzername); // 'Pete', Änderungen sind aus der Referenz „Benutzer“ ersichtlich
Es ist, als ob wir einen Schrank mit zwei Schlüsseln hätten und einen davon ( admin
) benutzen würden, um hineinzukommen und Änderungen vorzunehmen. Wenn wir dann später einen anderen Schlüssel ( user
) verwenden, öffnen wir immer noch denselben Schrank und können auf den geänderten Inhalt zugreifen.
Zwei Objekte sind nur dann gleich, wenn sie dasselbe Objekt sind.
Hier verweisen beispielsweise a
und b
auf dasselbe Objekt, sind also gleich:
sei a = {}; sei b = a; // Referenz kopieren alarm( a == b ); // wahr, beide Variablen verweisen auf dasselbe Objekt alarm( a === b ); // WAHR
Und hier sind zwei unabhängige Objekte nicht gleich, obwohl sie gleich aussehen (beide sind leer):
sei a = {}; sei b = {}; // zwei unabhängige Objekte alarm( a == b ); // FALSCH
Für Vergleiche wie obj1 > obj2
oder für einen Vergleich mit einem Grundelement obj == 5
werden Objekte in Grundelemente umgewandelt. Wir werden bald untersuchen, wie Objektkonvertierungen funktionieren, aber um die Wahrheit zu sagen, sind solche Vergleiche sehr selten nötig – normalerweise entstehen sie als Ergebnis eines Programmierfehlers.
Const-Objekte können geändert werden
Ein wichtiger Nebeneffekt der Speicherung von Objekten als Referenzen besteht darin, dass ein als const
deklariertes Objekt geändert werden kann .
Zum Beispiel:
const user = { Name: „John“ }; user.name = "Pete"; // (*) Alert(Benutzername); // Pete
Es scheint, dass die Zeile (*)
einen Fehler verursachen würde, aber das ist nicht der Fall. Der Wert von user
ist konstant, er muss immer auf dasselbe Objekt verweisen, die Eigenschaften dieses Objekts können jedoch frei geändert werden.
Mit anderen Worten: Der const user
gibt nur dann einen Fehler aus, wenn wir versuchen, user=...
als Ganzes festzulegen.
Wenn wir jedoch wirklich konstante Objekteigenschaften erstellen müssen, ist dies auch möglich, allerdings mit völlig anderen Methoden. Wir werden das im Kapitel Eigenschaftsflags und Deskriptoren erwähnen.
Durch das Kopieren einer Objektvariablen wird also ein weiterer Verweis auf dasselbe Objekt erstellt.
Was aber, wenn wir ein Objekt duplizieren müssen?
Wir können ein neues Objekt erstellen und die Struktur des vorhandenen Objekts replizieren, indem wir seine Eigenschaften durchlaufen und sie auf der primitiven Ebene kopieren.
So was:
let user = { Name: „John“, Alter: 30 }; let clone = {}; // das neue leere Objekt // Kopieren wir alle Benutzereigenschaften hinein for (Benutzer eingeben lassen) { clone[key] = user[key]; } // Jetzt ist der Klon ein völlig unabhängiges Objekt mit demselben Inhalt clone.name = "Pete"; // die darin enthaltenen Daten geändert alarm( user.name ); // immer noch John im Originalobjekt
Wir können auch die Methode Object.assign verwenden.
Die Syntax lautet:
Object.assign(dest, ...sources)
Das erste Argument dest
ist ein Zielobjekt.
Weitere Argumente sind eine Liste von Quellobjekten.
Es kopiert die Eigenschaften aller Quellobjekte in das Ziel dest
und gibt es dann als Ergebnis zurück.
Wir haben beispielsweise ein user
. Fügen wir ihm einige Berechtigungen hinzu:
let user = { name: "John" }; letpermissions1 = { canView: true }; letpermissions2 = { canEdit: true }; // kopiert alle Eigenschaften von „permissions1“ und „permissions2“ in den Benutzer Object.assign(Benutzer, Berechtigungen1, Berechtigungen2); // now user = { name: "John", canView: true, canEdit: true } Alert(Benutzername); // John alarm(user.canView); // WAHR alarm(user.canEdit); // WAHR
Wenn der kopierte Eigenschaftsname bereits vorhanden ist, wird er überschrieben:
let user = { name: "John" }; Object.assign(user, { name: „Pete“ }); Alert(Benutzername); // now user = { name: "Pete" }
Wir können Object.assign
auch verwenden, um ein einfaches Objektklonen durchzuführen:
let user = { Name: „John“, Alter: 30 }; let clone = Object.assign({}, user); alarm(clone.name); // John alarm(clone.age); // 30
Hier kopiert es alle Eigenschaften des user
in das leere Objekt und gibt es zurück.
Es gibt auch andere Methoden zum Klonen eines Objekts, z. B. die Verwendung der Spread-Syntax clone = {...user}
, die später im Tutorial behandelt wird.
Bisher gingen wir davon aus, dass alle Eigenschaften des user
primitiv sind. Eigenschaften können jedoch Verweise auf andere Objekte sein.
So was:
let user = { Name: „John“, Größen: { Höhe: 182, Breite: 50 } }; alarm( user.sizes.height ); // 182
Jetzt reicht es nicht aus, clone.sizes = user.sizes
zu kopieren, da user.sizes
ein Objekt ist und per Referenz kopiert wird, sodass clone
und user
die gleichen Größen haben:
let user = { Name: „John“, Größen: { Höhe: 182, Breite: 50 } }; let clone = Object.assign({}, user); alarm( user.sizes === clone.sizes ); // wahr, dasselbe Objekt // Benutzer- und Klonfreigabegrößen user.sizes.width = 60; // eine Eigenschaft von einer Stelle aus ändern alarm(clone.sizes.width); // 60, erhalte das Ergebnis vom anderen
Um das zu beheben und user
und clone
wirklich getrennte Objekte zu machen, sollten wir eine Klonschleife verwenden, die jeden Wert von user[key]
untersucht und, wenn es sich um ein Objekt handelt, auch dessen Struktur repliziert. Dies wird als „Deep Cloning“ oder „Structured Cloning“ bezeichnet. Es gibt die Methode „structuredClone“, die Deep Cloning implementiert.
Der Aufruf structuredClone(object)
klont das object
mit allen verschachtelten Eigenschaften.
So können wir es in unserem Beispiel verwenden:
let user = { Name: „John“, Größen: { Höhe: 182, Breite: 50 } }; let clone = structureClone(user); alarm( user.sizes === clone.sizes ); // false, verschiedene Objekte // Benutzer und Klon sind jetzt völlig unabhängig user.sizes.width = 60; // eine Eigenschaft von einer Stelle aus ändern alarm(clone.sizes.width); // 50, nicht verwandt
Die Methode structuredClone
kann die meisten Datentypen klonen, z. B. Objekte, Arrays und Grundwerte.
Es unterstützt auch Zirkelverweise, wenn eine Objekteigenschaft auf das Objekt selbst verweist (direkt oder über eine Kette oder Referenzen).
Zum Beispiel:
let user = {}; // Lass uns einen Zirkelverweis erstellen: // user.me verweist auf den Benutzer selbst user.me = Benutzer; let clone = structureClone(user); alarm(clone.me === clone); // WAHR
Wie Sie sehen, verweist clone.me
auf den clone
, nicht auf den user
! Der Zirkelverweis wurde also auch korrekt geklont.
Allerdings gibt es Fälle, in denen structuredClone
fehlschlägt.
Wenn ein Objekt beispielsweise eine Funktionseigenschaft hat:
// Fehler strukturierter Klon({ f: function() {} });
Funktionseigenschaften werden nicht unterstützt.
Um solche komplexen Fälle zu bewältigen, müssen wir möglicherweise eine Kombination aus Klonmethoden verwenden, benutzerdefinierten Code schreiben oder, um das Rad nicht neu zu erfinden, eine vorhandene Implementierung übernehmen, beispielsweise _.cloneDeep(obj) aus der JavaScript-Bibliothek lodash.
Objekte werden per Referenz zugewiesen und kopiert. Mit anderen Worten: Eine Variable speichert nicht den „Objektwert“, sondern eine „Referenz“ (Adresse im Speicher) für den Wert. Wenn Sie also eine solche Variable kopieren oder als Funktionsargument übergeben, wird diese Referenz kopiert, nicht das Objekt selbst.
Alle Vorgänge über kopierte Referenzen (wie das Hinzufügen/Entfernen von Eigenschaften) werden für dasselbe einzelne Objekt ausgeführt.
Um eine „echte Kopie“ (einen Klon) zu erstellen, können wir Object.assign
für die sogenannte „flache Kopie“ (verschachtelte Objekte werden durch Referenz kopiert) oder eine „Deep Cloning“-Funktion structuredClone
verwenden oder eine benutzerdefinierte Klonimplementierung verwenden, z als _.cloneDeep(obj).