Die Plattform wird jetzt mit structuredClone() ausgeliefert, einer integrierten Funktion zum Erstellen von Deep-Copies.
Lange Zeit mussten Sie auf Workarounds und Bibliotheken zurückgreifen, um eine Deep Copy eines JavaScript-Werts zu erstellen. Die Plattform wird jetzt mit structuredClone()
ausgeliefert, einer integrierten Funktion zum Erstellen von Deepcopies.
Flache Kopien
Das Kopieren eines Werts in JavaScript ist fast immer flach und nicht tief. Das bedeutet, dass Änderungen an tief verschachtelten Werten sowohl in der Kopie als auch im Original sichtbar sind.
Eine Möglichkeit, in JavaScript eine flache Kopie zu erstellen, ist die Verwendung des Objekt-Spread-Operators ...
:
const myOriginal = {
someProp: "with a string value",
anotherProp: {
withAnotherProp: 1,
andAnotherProp: true
}
};
const myShallowCopy = {...myOriginal};
Wenn Sie einer flachen Kopie eine Eigenschaft hinzufügen oder eine Eigenschaft direkt in der flachen Kopie ändern, wirkt sich das nur auf die Kopie aus, nicht auf das Original:
myShallowCopy.aNewProp = "a new value";
console.log(myOriginal.aNewProp)
// ^ logs `undefined`
Wenn Sie jedoch eine tief verschachtelte Eigenschaft hinzufügen oder ändern, wirkt sich das sowohl auf die Kopie als auch auf das Original aus:
myShallowCopy.anotherProp.aNewProp = "a new value";
console.log(myOriginal.anotherProp.aNewProp)
// ^ logs `a new value`
Der Ausdruck {...myOriginal}
durchläuft die (aufzählbaren) Eigenschaften von myOriginal
mithilfe des Spread-Operators. Dabei werden der Attributname und der Attributwert verwendet und nacheinander einem neu erstellten, leeren Objekt zugewiesen. Das resultierende Objekt hat also dieselbe Form, aber eine eigene Kopie der Liste der Eigenschaften und Werte. Die Werte werden ebenfalls kopiert, aber sogenannte primitive Werte werden vom JavaScript-Wert anders behandelt als nicht primitive Werte. MDN zufolge:
In JavaScript sind primitive Werte (primitive value, primitive data type) Daten, die kein Objekt sind und keine Methoden haben. Es gibt sieben einfache Datentypen: „string“, „number“, „bigint“, „boolean“, „undefined“, „symbol“ und „null“.
MDN – Primitive
Nicht primitive Werte werden als Referenzen behandelt. Das Kopieren des Werts bedeutet also nur, dass eine Referenz auf dasselbe zugrunde liegende Objekt kopiert wird. Dies führt zum Verhalten der flachen Kopie.
Tiefe Kopien
Das Gegenteil einer oberflächlichen Kopie ist eine vollständige Kopie. Bei einem Algorithmus für das tiefe Kopieren werden die Eigenschaften eines Objekts ebenfalls einzeln kopiert. Wenn jedoch eine Referenz auf ein anderes Objekt gefunden wird, ruft sich der Algorithmus rekursiv selbst auf und erstellt auch eine Kopie dieses Objekts. Das kann sehr wichtig sein, um sicherzustellen, dass zwei Codeabschnitte nicht versehentlich ein Objekt gemeinsam nutzen und unwissentlich den Zustand des jeweils anderen manipulieren.
Bisher gab es in JavaScript keine einfache oder gute Möglichkeit, eine Deep Copy eines Werts zu erstellen. Viele Nutzer haben sich auf Drittanbieterbibliotheken wie die cloneDeep()
-Funktion von Lodash verlassen. Die wohl häufigste Lösung für dieses Problem war ein JSON-basierter Hack:
const myDeepCopy = JSON.parse(JSON.stringify(myOriginal));
Tatsächlich war dies ein so beliebter Workaround, dass V8 aggressiv optimiert JSON.parse()
und insbesondere das obige Muster, um es so schnell wie möglich zu machen. Das Verfahren ist zwar schnell, hat aber einige Nachteile und Fallstricke:
- Rekursive Datenstrukturen:
JSON.stringify()
gibt einen Fehler aus, wenn Sie eine rekursive Datenstruktur angeben. Das kann ganz leicht passieren, wenn Sie mit verknüpften Listen oder Bäumen arbeiten. - Integrierte Typen:
JSON.stringify()
löst einen Fehler aus, wenn der Wert andere integrierte JS-Typen wieMap
,Set
,Date
,RegExp
oderArrayBuffer
enthält. - Funktionen:
JSON.stringify()
verwirft Funktionen stillschweigend.
Strukturiertes Klonen
Die Plattform musste bereits an einigen Stellen in der Lage sein, Deep Copies von JavaScript-Werten zu erstellen: Wenn ein JS-Wert in IndexedDB gespeichert wird, ist eine Form der Serialisierung erforderlich, damit er auf der Festplatte gespeichert und später deserialisiert werden kann, um den JS-Wert wiederherzustellen. Ebenso erfordert das Senden von Nachrichten an einen WebWorker über postMessage()
die Übertragung eines JS-Werts von einem JS-Bereich in einen anderen. Der dafür verwendete Algorithmus wird als „Structured Clone“ bezeichnet und war bis vor Kurzem für Entwickler nicht ohne Weiteres zugänglich.
Das hat sich nun geändert. Die HTML-Spezifikation wurde geändert, um eine Funktion namens structuredClone()
verfügbar zu machen, die genau diesen Algorithmus ausführt. So können Entwickler ganz einfach Deep Copies von JavaScript-Werten erstellen.
const myDeepCopy = structuredClone(myOriginal);
Geschafft! Das ist die gesamte API. Wenn Sie mehr über die Details erfahren möchten, lesen Sie den MDN-Artikel.
Funktionen und Einschränkungen
Das strukturierte Klonen behebt viele (aber nicht alle) Nachteile der JSON.stringify()
-Technik. Beim strukturierten Klonen können zyklische Datenstrukturen verarbeitet werden. Außerdem werden viele integrierte Datentypen unterstützt. Das Verfahren ist in der Regel robuster und oft schneller.
Es gibt jedoch einige Einschränkungen, die Sie möglicherweise überraschen:
- Prototypen: Wenn Sie
structuredClone()
mit einer Klasseninstanz verwenden, erhalten Sie ein einfaches Objekt als Rückgabewert, da beim strukturierten Klonen die Prototypenkette des Objekts verworfen wird. - Funktionen: Wenn Ihr Objekt Funktionen enthält, löst
structuredClone()
eineDataCloneError
-Ausnahme aus. - Nicht klonbare Werte: Einige Werte sind nicht strukturiert klonbar, insbesondere
Error
und DOM-Knoten. Dadurch wirdstructuredClone()
ausgelöst.
Wenn eine dieser Einschränkungen für Ihren Anwendungsfall ein Problem darstellt, bieten Bibliotheken wie Lodash weiterhin benutzerdefinierte Implementierungen anderer Deep-Cloning-Algorithmen, die für Ihren Anwendungsfall geeignet sein können.
Leistung
Ich habe zwar keinen neuen Mikro-Benchmark-Vergleich durchgeführt, aber Anfang 2018, bevor structuredClone()
verfügbar war. Damals war JSON.parse()
die schnellste Option für sehr kleine Objekte. Ich gehe davon aus, dass das so bleibt. Techniken, die auf strukturiertem Klonen basierten, waren bei größeren Objekten (deutlich) schneller. Da die neue structuredClone()
ohne den Overhead anderer APIs auskommt und robuster als JSON.parse()
ist, empfehle ich, sie als Standardmethode zum Erstellen von Deep-Kopien zu verwenden.
Fazit
Wenn Sie in JavaScript eine Deep-Copy eines Werts erstellen müssen, z. B. weil Sie unveränderliche Datenstrukturen verwenden oder sicherstellen möchten, dass eine Funktion ein Objekt bearbeiten kann, ohne das Original zu beeinträchtigen, sind keine Workarounds oder Bibliotheken mehr erforderlich. Das JS-Ökosystem hat jetzt structuredClone()
. Hurra.