Objekte sind die grundlegenden Bausteine von JavaScript. Ein Objekt ist eine Sammlung von Eigenschaften, und eine Eigenschaft ist eine Verbindung zwischen einem Schlüssel (oder Namen) und einem Wert. Fast alle Objekte in JavaScript sind Instanzen von Object
, das an der Spitze der Prototypenkette steht.
Einführung
Wie Sie wissen, erzeugt der Zuweisungsoperator keine Kopie eines Objekts, sondern weist ihm nur eine Referenz zu. Betrachten wir den folgenden Code:
let obj = { a: 1, b: 2,};let copy = obj;obj.a = 5;console.log(copy.a);// Result // a = 5;
Die obj
-Variable ist ein Container für das neu initialisierte Objekt. Die copy
-Variable zeigt auf das gleiche Objekt und ist eine Referenz auf dieses Objekt. Im Grunde sagt also dieses { a: 1, b: 2, }
Objekt aus: Es gibt jetzt zwei Wege, um auf mich zuzugreifen. Du musst durch die obj
-Variable oder die copy
-Variable gehen – beide Wege führen zu mir, und alles, was du über diese Wege (Gateways) mit mir machst, wird sich auf mich auswirken.
Immutabilität ist heutzutage in aller Munde, und du musst auf diesen Aufruf hören! Diese Methode beseitigt jede Form von Unveränderlichkeit und könnte zu Fehlern führen, wenn das ursprüngliche Objekt von einem anderen Teil Ihres Codes verwendet wird.
Der naive Weg des Kopierens von Objekten
Der naive Weg des Kopierens von Objekten besteht darin, das ursprüngliche Objekt in einer Schleife zu durchlaufen und jede Eigenschaft eine nach der anderen zu kopieren. Werfen wir einen Blick auf diesen Code:
function copy(mainObj) { let objCopy = {}; // objCopy will store a copy of the mainObj let key; for (key in mainObj) { objCopy = mainObj; // copies each property to the objCopy object } return objCopy;}const mainObj = { a: 2, b: 5, c: { x: 7, y: 4, },}console.log(copy(mainObj));
Inhärente Probleme
-
objCopy
Objekt hat eine neueObject.prototype
Methode, die sich von dermainObj
Objektprototyp-Methode unterscheidet, was nicht das ist, was wir wollen. Wir wollen eine exakte Kopie des Originalobjekts. - Eigenschaftsdeskriptoren werden nicht kopiert. Ein „writable“-Deskriptor, dessen Wert auf „false“ gesetzt ist, wird im
objCopy
-Objekt „true“ sein. - Der obige Code kopiert nur aufzählbare Eigenschaften von
mainObj
. - Wenn eine der Eigenschaften im Originalobjekt selbst ein Objekt ist, dann wird sie von der Kopie und dem Original gemeinsam genutzt, so dass ihre jeweiligen Eigenschaften auf das gleiche Objekt zeigen.
Objekte oberflächlich kopieren
Ein Objekt wird als oberflächlich kopiert bezeichnet, wenn die obersten Eigenschaften der Quelle ohne Verweis kopiert werden und eine Quelleigenschaft existiert, deren Wert ein Objekt ist und als Verweis kopiert wird. Wenn der Quellwert eine Referenz auf ein Objekt ist, wird nur dieser Referenzwert in das Zielobjekt kopiert.
Eine flache Kopie dupliziert die Eigenschaften der obersten Ebene, aber das verschachtelte Objekt wird zwischen dem Original (Quelle) und der Kopie (Ziel) geteilt.
Verwendung der Methode Object.assign()
Die Methode Object.assign() wird verwendet, um die Werte aller aufzählbaren eigenen Eigenschaften von einem oder mehreren Quellobjekten in ein Zielobjekt zu kopieren.
let obj = { a: 1, b: 2,};let objCopy = Object.assign({}, obj);console.log(objCopy);// Result - { a: 1, b: 2 }
So weit, so gut. Wir haben eine Kopie von obj
erstellt. Schauen wir mal, ob die Unveränderlichkeit gegeben ist:
let obj = { a: 1, b: 2,};let objCopy = Object.assign({}, obj);console.log(objCopy); // result - { a: 1, b: 2 }objCopy.b = 89;console.log(objCopy); // result - { a: 1, b: 89 }console.log(obj); // result - { a: 1, b: 2 }
Im obigen Code, haben wir den Wert der Eigenschaft 'b'
im objCopy
-Objekt auf 89
geändert und wenn wir das geänderte objCopy
-Objekt in der Konsole protokollieren, gelten die Änderungen nur für objCopy
. Die letzte Codezeile überprüft, ob das obj
-Objekt noch intakt ist und sich nicht verändert hat. Dies impliziert, dass wir erfolgreich eine Kopie des Quellobjekts ohne jegliche Referenzen darauf erstellt haben.
Fallstrick von Object.assign()
Nicht so schnell! Wir haben zwar erfolgreich eine Kopie erstellt und alles scheint gut zu funktionieren, aber erinnern Sie sich, dass wir über das oberflächliche Kopieren gesprochen haben? Lassen Sie uns einen Blick auf dieses Beispiel werfen:
let obj = { a: 1, b: { c: 2, },}let newObj = Object.assign({}, obj);console.log(newObj); // { a: 1, b: { c: 2} }obj.a = 10;console.log(obj); // { a: 10, b: { c: 2} }console.log(newObj); // { a: 1, b: { c: 2} }newObj.a = 20;console.log(obj); // { a: 10, b: { c: 2} }console.log(newObj); // { a: 20, b: { c: 2} }newObj.b.c = 30;console.log(obj); // { a: 10, b: { c: 30} }console.log(newObj); // { a: 20, b: { c: 30} }// Note: newObj.b.c = 30; Read why..
Warum ist obj.b.c = 30?
Tja, das ist ein Fallstrick von Object.assign()
Object.assign
macht nur oberflächliche Kopien. Sowohl newObj.b
als auch obj.b
teilen sich denselben Verweis auf das Objekt, da keine einzelnen Kopien gemacht wurden, sondern ein Verweis auf das Objekt kopiert wurde. Jede Änderung, die an einer der Eigenschaften des Objekts vorgenommen wird, gilt für alle Referenzen, die das Objekt verwenden. Wie können wir dies beheben? Lesen Sie weiter… wir haben eine Lösung im nächsten Abschnitt.
Hinweis: Eigenschaften in der Prototypenkette und nicht aufzählbare Eigenschaften können nicht kopiert werden. Siehe hier:
let someObj = { a: 2,}let obj = Object.create(someObj, { b: { value: 2, }, c: { value: 3, enumerable: true, },});let objCopy = Object.assign({}, obj);console.log(objCopy); // { c: 3 }
-
someObj
befindet sich in der Prototypenkette von obj und kann daher nicht kopiert werden. -
property b
ist eine nicht aufzählbare Eigenschaft. -
property c
hat einen aufzählbaren Eigenschaftsdeskriptor, der es ermöglicht, aufzählbar zu sein. Deshalb wurde es kopiert.
Objekte tief kopieren
Eine tiefe Kopie dupliziert jedes Objekt, auf das sie trifft. Die Kopie und das Originalobjekt haben nichts gemeinsam, es ist also eine Kopie des Originals. Hier ist die Lösung für das Problem, das wir mit Object.assign()
hatten. Schauen wir es uns an.
Verwendung von JSON.parse(JSON.stringify(object));
Damit ist das Problem behoben, das wir vorher hatten. Jetzt hat newObj.b
eine Kopie und nicht eine Referenz! Dies ist eine Möglichkeit, Objekte tief zu kopieren. Hier ist ein Beispiel:
let obj = { a: 1, b: { c: 2, },}let newObj = JSON.parse(JSON.stringify(obj));obj.b.c = 20;console.log(obj); // { a: 1, b: { c: 20 } }console.log(newObj); // { a: 1, b: { c: 2 } } (New Object Intact!)
Immutable: ✓
Pitfall
Leider kann diese Methode nicht verwendet werden, um benutzerdefinierte Objektmethoden zu kopieren. Siehe unten.
Kopieren von Objektmethoden
Eine Methode ist eine Eigenschaft eines Objekts, die eine Funktion ist. In den bisherigen Beispielen haben wir noch kein Objekt mit einer Methode kopiert. Das wollen wir jetzt nachholen und die Methoden, die wir gelernt haben, zum Kopieren verwenden.
let obj = { name: 'scotch.io', exec: function exec() { return true; },}let method1 = Object.assign({}, obj);let method2 = JSON.parse(JSON.stringify(obj));console.log(method1); //Object.assign({}, obj)/* result{ exec: function exec() { return true; }, name: "scotch.io"}*/console.log(method2); // JSON.parse(JSON.stringify(obj))/* result{ name: "scotch.io"}*/
Das Ergebnis zeigt, dass Object.assign()
zum Kopieren von Methoden verwendet werden kann, während JSON.parse(JSON.stringify(obj))
nicht verwendet werden kann.
Kopieren von kreisförmigen Objekten
Kreisförmige Objekte sind Objekte, die Eigenschaften haben, die sich selbst referenzieren. Lassen Sie uns die Methoden zum Kopieren von Objekten verwenden, die wir bisher gelernt haben, um Kopien eines kreisförmigen Objekts zu erstellen und zu sehen, ob es funktioniert.
Mit JSON.parse(JSON.stringify(object))
Lassen Sie uns JSON.parse(JSON.stringify(object))
versuchen:
// circular objectlet obj = { a: 'a', b: { c: 'c', d: 'd', },}obj.c = obj.b;obj.e = obj.a;obj.b.c = obj.c;obj.b.d = obj.b;obj.b.e = obj.b.c;let newObj = JSON.parse(JSON.stringify(obj));console.log(newObj);
Hier ist das Ergebnis:
JSON.parse(JSON.stringify(obj))
funktioniert eindeutig nicht für kreisförmige Objekte.
Benutzung von Object.assign()
Lassen Sie uns Object.assign()
versuchen:
// circular objectlet obj = { a: 'a', b: { c: 'c', d: 'd', },}obj.c = obj.b;obj.e = obj.a;obj.b.c = obj.c;obj.b.d = obj.b;obj.b.e = obj.b.c;let newObj2 = Object.assign({}, obj);console.log(newObj2);
Hier ist das Ergebnis:
Object.assign()
funktioniert gut für das flache Kopieren kreisförmiger Objekte, würde aber nicht für das tiefe Kopieren funktionieren. Schauen Sie sich das circular object tree
in der Konsole Ihres Browsers an. Ich bin mir sicher, dass Sie dort eine Menge interessanter Dinge finden werden.
Verwendung von Spread-Elementen ( … )
ES6 hat bereits Rest-Elemente für Array-Destrukturierungszuweisungen und Spread-Elemente für Array-Literale implementiert. Sehen Sie sich hier die Implementierung von Spread-Elementen auf einem Array an:
const array = ;const newArray = ;console.log(newArray);// Result //
Die Spread-Eigenschaft für Objektliterale ist derzeit ein Stage-3-Vorschlag für ECMAScript. Spread-Eigenschaften in Objektinitialisierern kopieren eigene aufzählbare Eigenschaften von einem Quellobjekt auf das Zielobjekt. Das folgende Beispiel zeigt, wie einfach es wäre, ein Objekt zu kopieren, wenn der Vorschlag angenommen wurde.
let obj = { one: 1, two: 2,}let newObj = { ...z };// { one: 1, two: 2 }
Hinweis: Dies ist nur für flache Kopien wirksam
Fazit
Objekte in JavaScript zu kopieren kann ziemlich entmutigend sein, besonders wenn Sie neu in JavaScript sind und sich nicht mit der Sprache auskennen. Hoffentlich hat Ihnen dieser Artikel geholfen, das Kopieren von Objekten zu verstehen und künftige Fallstricke zu vermeiden, auf die Sie stoßen könnten. Wenn Sie eine Bibliothek oder ein Stück Code haben, das ein besseres Ergebnis erzielt, können Sie es gerne mit der Community teilen. Viel Spaß beim Kodieren!