Deeplink in JavaScript mit StructuredClone

Die Plattform wird jetzt mit „StructuredClone()“ ausgeliefert, einer integrierten Funktion für die Deep-Copy-Funktion.

Am längsten war es, Umgehungslösungen und Bibliotheken zu nutzen, um eine tiefe Kopie eines JavaScript-Werts zu erstellen. Die Plattform wird jetzt mit structuredClone() ausgeliefert, einer integrierten Funktion für Deep-Copy-Prozesse.

Unterstützte Browser

  • 98
  • 98
  • 94
  • 15,4

Quelle

Flache Kopien

Das Kopieren eines Werts in JavaScript ist im Gegensatz zu tief fast immer oberflächlich. Das bedeutet, dass Änderungen an tief verschachtelten Werten sowohl in der Kopie als auch im Original sichtbar sind.

Eine Möglichkeit, eine oberflächliche Kopie in JavaScript mit dem Objektverteilungsoperator ... zu erstellen:

const myOriginal = {
  someProp: "with a string value",
  anotherProp: {
    withAnotherProp: 1,
    andAnotherProp: true
  }
};

const myShallowCopy = {...myOriginal};

Das Hinzufügen oder Ändern einer Eigenschaft direkt in der oberflächlichen Kopie wirkt sich nur auf die Kopie aus, nicht auf das Original:

myShallowCopy.aNewProp = "a new value";
console.log(myOriginal.aNewProp)
// ^ logs `undefined`

Wird eine tief verschachtelte Property hinzugefügt oder geändert, wirkt sich das jedoch 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} iteriert über die (aufzählbaren) Attribute von myOriginal mithilfe des Spread-Operators. Dabei werden der Name und der Wert der Eigenschaft verwendet und nacheinander einem neu erstellten, leeren Objekt zugewiesen. Daher hat das resultierende Objekt dieselbe Form, hat 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. So zitieren Sie MDN:

In JavaScript handelt es sich bei einem primitiven (primitiven Wert, primitiven Datentyp) um Daten, die kein Objekt sind und über keine Methoden verfügen. Es gibt sieben primitive Datentypen: Zeichenfolge, Zahl, Bigint, boolesch, nicht definiert, Symbol und Null.

MDN – Primitive

Nicht primitive Werte werden als references gehandhabt. Das bedeutet, dass beim Kopieren des Werts eigentlich nur ein Verweis auf dasselbe zugrunde liegende Objekt kopiert wird, was zu einem oberflächlichen Kopierverhalten führt.

Deep-Kopien

Das Gegenteil einer oberflächlichen Kopie ist eine tiefe Kopie. Ein Deep-Copy-Algorithmus kopiert auch die Eigenschaften eines Objekts einzeln, ruft sich aber rekursiv auf, wenn er einen Verweis auf ein anderes Objekt findet, wodurch ebenfalls eine Kopie dieses Objekts erstellt wird. Dies kann sehr wichtig sein, um sicherzustellen, dass zwei Code-Snippets nicht versehentlich ein Objekt freigeben und unwissentlich den Zustand der anderen beeinflussen.

Früher gab es keine einfache oder schöne Möglichkeit, eine Deep-Kopie eines Werts in JavaScript zu erstellen. Viele Menschen verlassen sich auf Bibliotheken von Drittanbietern wie die cloneDeep()-Funktion von Lodash. Die wohl häufigste Lösung für dieses Problem war ein JSON-basierter Hack:

const myDeepCopy = JSON.parse(JSON.stringify(myOriginal));

Das war eine so beliebte Problemumgehung, dass JSON.parse() und insbesondere das obige Muster in V8 aggressiv optimiert wurden, um die Geschwindigkeit so schnell wie möglich zu verkürzen. Und obwohl es schnell ist, hat es einige Mängel und Stolperdraht:

  • Rekursive Datenstrukturen: JSON.stringify() wird ausgegeben, wenn Sie eine rekursive Datenstruktur festlegen. Dies kann leicht passieren, wenn Sie mit verknüpften Listen oder Bäumen arbeiten.
  • Integrierte Typen: JSON.stringify() wird ausgelöst, wenn der Wert andere integrierte JS-Elemente wie Map, Set, Date, RegExp oder ArrayBuffer enthält.
  • Funktionen: JSON.stringify() verwirft automatisch Funktionen.

Strukturiertes Klonen

Die Plattform benötigte an verschiedenen Stellen bereits die Möglichkeit, Deep-Kopien von JavaScript-Werten zu erstellen: Das Speichern eines JS-Werts in IndexedDB erfordert eine Form der Serialisierung, damit er auf der Festplatte gespeichert und später deserialisiert werden kann, um den JS-Wert wiederherzustellen. Ebenso muss beim Senden von Nachrichten an einen WebWorker über postMessage() ein JS-Wert von einem JS-Bereich in einen anderen übertragen werden. Der hierfür verwendete Algorithmus ist „Strukturierter Klon“ und war bis vor Kurzem nicht ohne Weiteres für Entwickler zugänglich.

Das hat sich nun geändert! Die HTML-Spezifikation wurde geändert, um eine Funktion namens structuredClone() zur Verfügung zu stellen, die genau diesen Algorithmus ausführt. So können Entwickler problemlos detaillierte Kopien von JavaScript-Werten erstellen.

const myDeepCopy = structuredClone(myOriginal);

Geschafft! Das ist die gesamte API. Weitere Informationen findest du im MDN-Artikel.

Funktionen und Einschränkungen

Beim strukturierten Klonen werden viele (aber nicht alle) Nachteile der JSON.stringify()-Technik behoben. Beim strukturierten Klonen lassen sich zyklische Datenstrukturen verarbeiten und viele integrierte Datentypen unterstützen. Außerdem ist es im Allgemeinen robuster und oft schneller.

Allerdings gibt es dennoch einige Einschränkungen, die Sie möglicherweise überrascht:

  • Prototypen: Wenn Sie structuredClone() mit einer Klasseninstanz verwenden, erhalten Sie ein einfaches Objekt als Rückgabewert, da beim strukturierten Klonen die Prototypkette des Objekts verworfen wird.
  • Funktionen: Wenn Ihr Objekt Funktionen enthält, löst structuredClone() die Ausnahme DataCloneError aus.
  • Nicht klonbare Werte: Einige Werte sind nicht strukturierte klonbare Werte, insbesondere Error- und DOM-Knoten. Dies führt dazu, dass structuredClone() ausgelöst wird.

Wenn eine dieser Einschränkungen für Ihren Anwendungsfall ein Problem darstellt, bieten Bibliotheken wie Lodash dennoch benutzerdefinierte Implementierungen anderer Deep-Cloning-Algorithmen an, die für Ihren Anwendungsfall geeignet sind oder nicht.

Leistung

Ich habe zwar keinen neuen Mikro-Benchmark-Vergleich vorgenommen, aber Anfang 2018, bevor structuredClone() angezeigt wurde. Damals war JSON.parse() die schnellste Option für sehr kleine Objekte. Ich gehe davon aus, dass das auch so bleibt. Techniken, die auf dem strukturierten Klonen basierten, waren bei größeren Objekten (deutlich) schneller. Da das neue structuredClone() ohne den Aufwand für den Missbrauch anderer APIs ist und robuster als JSON.parse() ist, empfehlen wir Ihnen, es als Standardansatz zum Erstellen von Deep-Kopien festzulegen.

Fazit

Wenn Sie eine Deep-Kopie eines Werts in JS erstellen müssen – vielleicht, weil Sie unveränderliche Datenstrukturen verwenden oder sicherstellen möchten, dass eine Funktion ein Objekt bearbeiten kann, ohne das Original zu beeinflussen – müssen Sie nicht mehr nach Problemumgehungen oder Bibliotheken suchen. Das JS-System verfügt jetzt über structuredClone(). Hurra!