Wie kann man ein Javascript-Objekt tief klonen?
Ich weiß , gibt es verschiedene Funktionen auf Basis von Frameworks wie JSON.parse(JSON.stringify(o))
und $.extend(true, {}, o)
aber ich möchte nicht , einen Rahmen, so verwenden.
Was ist der eleganteste oder effizienteste Weg, um einen tiefen Klon zu erstellen?
Wir kümmern uns um Randfälle wie das Klonen von Arrays. Prototypenketten nicht brechen, sich mit Selbstreferenz befassen.
Es ist uns nicht wichtig, das Kopieren von DOM-Objekten und Ähnlichem zu unterstützen, da .cloneNode
es aus diesem Grund existiert.
Da ich hauptsächlich tiefe Klone für die node.js
Verwendung der ES5-Funktionen der V8-Engine verwenden möchte, ist dies akzeptabel.
[Bearbeiten]
Bevor jemand vorschlägt, möchte ich erwähnen, dass es einen deutlichen Unterschied zwischen dem Erstellen einer Kopie durch prototypisches Erben vom Objekt und dem Klonen gibt . Ersteres bringt die Prototypenkette durcheinander.
[Weiter bearbeiten]
Nachdem ich Ihre Antwort gelesen hatte, kam ich zu der nervigen Entdeckung, dass das Klonen ganzer Objekte ein sehr gefährliches und schwieriges Spiel ist. Nehmen Sie zum Beispiel das folgende schließungsbasierte Objekt
var o = (function() {
var magic = 42;
var magicContainer = function() {
this.get = function() { return magic; };
this.set = function(i) { magic = i; };
}
return new magicContainer;
}());
var n = clone(o); // how to implement clone to support closures
Gibt es eine Möglichkeit, eine Klonfunktion zu schreiben, die das Objekt klont, zum Zeitpunkt des Klonens denselben Status hat, den Status jedoch nicht ändern kann, o
ohne einen JS-Parser in JS zu schreiben?
Es sollte keine reale Welt mehr für eine solche Funktion geben. Dies ist nur akademisches Interesse.
quelle
clone
Funktion nicht zugänglich sind . Das betreffende Objekt sollte eine eigene maßgeschneiderteclone
Methode bereitstellen.Antworten:
Es kommt wirklich darauf an, was Sie klonen möchten. Ist dies ein echtes JSON-Objekt oder nur ein Objekt in JavaScript? Wenn Sie einen Klon erstellen möchten, kann dies zu Problemen führen. Welches Problem? Ich werde es unten erklären, aber zuerst ein Codebeispiel, das Objektliterale, alle Grundelemente, Arrays und DOM-Knoten klont.
Lassen Sie uns nun über Probleme sprechen, die beim Klonen von REAL-Objekten auftreten können. Ich spreche jetzt von Objekten, die Sie erstellen, indem Sie so etwas tun
Natürlich können Sie sie klonen, es ist kein Problem, jedes Objekt macht Konstruktoreigenschaften verfügbar und Sie können sie zum Klonen von Objekten verwenden, aber es funktioniert nicht immer. Sie können
for in
diese Objekte auch einfach bearbeiten, aber es geht in die gleiche Richtung - Probleme. Ich habe auch Klonfunktionen in den Code aufgenommen, diese werden jedoch durchif( false )
Anweisungen ausgeschlossen .Warum kann das Klonen ein Schmerz sein? Zunächst einmal könnte jedes Objekt / jede Instanz einen bestimmten Status haben. Sie können nie sicher sein, dass Ihre Objekte beispielsweise keine privaten Variablen haben, und wenn dies der Fall ist, brechen Sie durch Klonen von Objekten einfach den Status.
Stellen Sie sich vor, es gibt keinen Staat, das ist in Ordnung. Dann haben wir noch ein anderes Problem. Das Klonen über die "Konstruktor" -Methode gibt uns ein weiteres Hindernis. Es ist eine Argumentabhängigkeit. Sie können nie sicher sein, dass jemand, der dieses Objekt erstellt hat, es nicht getan hat
Wenn dies der Fall ist, haben Sie kein Glück, someBikeInstance wurde wahrscheinlich in einem bestimmten Kontext erstellt und dieser Kontext ist für die Klonmethode nicht bekannt.
Was tun? Sie können immer noch
for in
Lösungen finden und solche Objekte wie normale Objektliterale behandeln, aber vielleicht ist es eine Idee, solche Objekte überhaupt nicht zu klonen und nur die Referenz dieses Objekts zu übergeben?Eine andere Lösung ist - Sie könnten eine Konvention festlegen, dass alle Objekte, die geklont werden müssen, diesen Teil selbst implementieren und eine geeignete API-Methode (wie cloneObject) bereitstellen sollten. Etwas, was
cloneNode
für DOM getan wird.Du entscheidest.
quelle
result = new item.constructor();
der schlecht ist, ist, dass Sie angesichts der Konstruktorfunktion und des Elementobjekts in der Lage sein sollten, alle an den Konstruktor übergebenen Parameter zu RE.false && item.constructor
? Ist das nichtif
nutzlos?if
ist aus funktionaler Sicht "nutzlos", weil es niemals ausgeführt wird, aber es hat den akademischen Zweck, eine hypothetische Implementierung zu zeigen, die man versuchen könnte, die der Autor aus dem später erläuterten Grund nicht empfiehlt. Ja, es wirdelse
jedes Mal eine Klausel auslösen , wenn die Bedingung ausgewertet wird, und dennoch gibt es einen Grund dafür, dass der Code vorhanden ist.Sehr einfacher Weg, vielleicht zu einfach:
quelle
cloneDeep
Hilfsfunktion wie in Lodash.Eine bessere Lösung ist die Verwendung einer Deep Copy-Funktion. Die folgende Funktion kopiert Objekte tief und erfordert keine Bibliothek eines Drittanbieters (jQuery, LoDash usw.).
quelle
var o = { a:1, b:2 } ; o["oo"] = { c:3, m:o };
bObject[k] = (v === null) ? null : (typeof v === "object") ? copy(v) : v;
Hier ist eine ES6-Funktion, die auch für Objekte mit zyklischen Referenzen funktioniert:
Ein Hinweis zu Sets und Maps
Wie mit den Tasten der Sätze behandeln und Karten ist umstritten: die Schlüssel sind oft Primitiven (in diesem Fall gibt es keine Debatte), aber sie können auch Objekte sein. In diesem Fall stellt sich die Frage: Sollten diese Schlüssel geklont werden?
Man könnte argumentieren, dass dies getan werden sollte, so dass, wenn diese Objekte in der Kopie mutiert sind, die Objekte im Original nicht betroffen sind und umgekehrt.
Auf der anderen Seite möchte man, dass, wenn ein
has
Schlüssel festgelegt / zugeordnet wird, dies sowohl im Original als auch in der Kopie zutrifft - zumindest bevor Änderungen an einem der beiden vorgenommen werden. Es wäre seltsam, wenn die Kopie ein Set / Map mit Schlüsseln wäre, die noch nie zuvor aufgetreten sind (wie sie während des Klonvorgangs erstellt wurden): Dies ist sicherlich nicht sehr nützlich für Code, der wissen muss, ob ein bestimmtes Objekt ein Objekt ist Geben Sie das Set / Map ein oder nicht.Wie Sie bemerken, bin ich eher der zweiten Meinung: Die Schlüssel von Sets und Maps sind Werte (möglicherweise Referenzen ), die gleich bleiben sollten.
Solche Auswahlmöglichkeiten tauchen häufig auch bei anderen (möglicherweise benutzerdefinierten) Objekten auf. Es gibt keine allgemeine Lösung, da viel davon abhängt, wie sich das geklonte Objekt in Ihrem speziellen Fall verhalten soll.
quelle
if (object instanceof Set) Array.from(object, val => result.add(deepClone(val, hash)));
Die Contrib-Bibliotheksbibliothek von Underscore.js verfügt über eine Funktion namens Snapshot , mit der ein Objekt tief geklont wird
Ausschnitt aus der Quelle:
Sobald die Bibliothek mit Ihrem Projekt verknüpft ist, rufen Sie die Funktion einfach mit auf
quelle
Dies ist die Deep-Cloning-Methode, die ich verwende. Ich finde sie großartig. Ich hoffe, Sie machen Vorschläge
quelle
Wie andere zu dieser und ähnlichen Fragen bemerkt haben, ist das Klonen eines "Objekts" im allgemeinen Sinne in JavaScript zweifelhaft.
Es gibt jedoch eine Klasse von Objekten, die ich "Daten" -Objekte nenne,
{ ... }
dh solche, die einfach aus Literalen und / oder einfachen Eigenschaftszuweisungen erstellt oder aus JSON deserialisiert wurden, für die es sinnvoll ist, klonen zu wollen. Gerade heute wollte ich die von einem Server empfangenen Daten künstlich um das Fünffache aufblasen, um zu testen, was für einen großen Datensatz passiert, aber das Objekt (ein Array) und seine untergeordneten Objekte mussten unterschiedliche Objekte sein, damit die Dinge richtig funktionieren. Durch Klonen konnte ich dies tun, um meinen Datensatz zu multiplizieren:Der andere Ort, an dem ich häufig Datenobjekte klone, ist das Zurücksenden von Daten an den Host, wo ich vor dem Senden Statusfelder vom Objekt im Datenmodell entfernen möchte. Zum Beispiel möchte ich möglicherweise alle Felder, die mit "_" beginnen, vom geklonten Objekt entfernen.
Dies ist der Code, den ich geschrieben habe, um dies generisch zu tun, einschließlich unterstützender Arrays und eines Selektors, um auszuwählen, welche Mitglieder geklont werden sollen (der eine "Pfad" -String verwendet, um den Kontext zu bestimmen):
Die einfachste vernünftige Deep-Clone-Lösung unter der Annahme eines Nicht-Null-Root-Objekts und ohne Elementauswahl ist:
quelle
Lo-Dash , jetzt eine Obermenge von Underscore.js , verfügt über einige Deep-Clone-Funktionen:
_.cloneDeep(object)
_.cloneDeepWith(object, (val) => {if(_.isElement(val)) return val.cloneNode(true)})
Der zweite Parameter ist eine Funktion, die aufgerufen wird, um den geklonten Wert zu erzeugen.
Aus einer Antwort des Autors selbst:
quelle
baseClone
einige Ideen liefern.Die folgende Funktion ist der effizienteste Weg, um Javascript-Objekte tief zu klonen.
quelle
Als reine Übung ist dies eine funktionalere Methode. Es ist eine Erweiterung von @ tfmontagues Antwort, da ich vorgeschlagen hatte , dort einen Wachblock hinzuzufügen. Aber da ich mich gezwungen fühle, ES6 zu verwenden und all die Dinge zu funktionalisieren, ist hier meine aufgemotzte Version. Dies verkompliziert die Logik, da Sie das Array zuordnen und über das Objekt reduzieren müssen, vermeidet jedoch Mutationen.
quelle
Mir ist aufgefallen, dass Map eine besondere Behandlung erfordern sollte. Bei allen Vorschlägen in diesem Thread lautet der Code daher:
quelle
meine Ergänzung zu allen Antworten
quelle
Dies funktioniert für Arrays, Objekte und Grundelemente. Doppelt rekursiver Algorithmus, der zwischen zwei Durchlaufmethoden wechselt:
quelle
Wir können die Rekursion verwenden, um deepCopy zu erstellen. Es kann eine Kopie von Array, Objekt, Array von Objekt, Objekt mit Funktion erstellen. Wenn Sie möchten, können Sie Funktionen für andere Arten von Datenstrukturen wie Karten usw. hinzufügen.
quelle
Verwenden Sie unveränderliche JS
Oder lodash / merge
quelle
Dieser mit Zirkelverweis funktioniert für mich
quelle
var newDate = neues Datum (this.oldDate); Ich habe oldDate an function übergeben und newDate aus this.oldDate generiert, aber es hat this.oldDate auch geändert. Also habe ich diese Lösung verwendet und es hat funktioniert.
quelle
Diese Lösung vermeidet Rekursionsprobleme bei Verwendung von [... Ziel] oder {... Ziel}
quelle