So klonen Sie eine Javascript ES6-Klasseninstanz

92

Wie klone ich eine Javascript-Klasseninstanz mit ES6?

Ich bin nicht an Lösungen interessiert, die auf jquery oder $ expand basieren.

Ich habe ziemlich alte Diskussionen über das Klonen von Objekten gesehen, die darauf hindeuten, dass das Problem ziemlich kompliziert ist, aber mit ES6 bietet sich eine sehr einfache Lösung an - ich werde es unten darlegen und sehen, ob die Leute es für zufriedenstellend halten.

edit: Es wird vorgeschlagen, dass meine Frage ein Duplikat ist. Ich habe diese Antwort gesehen, aber sie ist 7 Jahre alt und beinhaltet sehr komplizierte Antworten mit js vor ES6. Ich schlage vor, dass meine Frage, die ES6 ermöglicht, eine dramatisch einfachere Lösung hat.

Tom
quelle
2
Wenn Sie eine neue Antwort auf eine alte Frage zu Stack Overflow haben, fügen Sie diese Antwort zur ursprünglichen Frage hinzu. Erstellen Sie nicht nur eine neue.
Heretic Monkey
1
Ich sehe das Problem, mit dem Tom konfrontiert ist / war, da ES6-Klasseninstanzen anders funktionieren als "normale" Objekte.
CherryNerd
2
Außerdem stürzt der erste Code in der akzeptierten Antwort, die Ihr "mögliches Duplikat" liefert, tatsächlich ab, wenn ich versuche, ihn über eine Instanz einer ES6-Klasse
auszuführen
Ich denke, dies ist kein Duplikat, denn obwohl die ES6-Klasseninstanz ein Objekt ist, ist nicht jedes Objekt eine ES6-Klasseninstanz, und daher geht die andere Frage nicht auf das Problem dieser Frage ein.
Tomáš Zato - Wiedereinsetzung Monica
5
Es ist kein Duplikat. Die andere Frage betraf reine ObjectDaten, die als Dateninhaber verwendet wurden. Hier geht es um ES6 classes und das Problem, die Klassentypinformationen nicht zu verlieren. Es braucht eine andere Lösung.
Flori

Antworten:

108

Es ist kompliziert. Ich habe viel versucht, am Ende funktionierte dieser Einzeiler für meine benutzerdefinierten ES6-Klasseninstanzen:

let clone = Object.assign( Object.create( Object.getPrototypeOf(orig)), orig)

Es vermeidet, den Prototyp zu setzen, weil sie sagen , es den Code verlangsamt viel.

Es unterstützt Symbole, ist jedoch nicht perfekt für Getter / Setter und funktioniert nicht mit nicht aufzählbaren Eigenschaften (siehe Object.assign () -Dokumente ). Außerdem scheint das Klonen grundlegender interner Klassen (wie Array, Datum, RegExp, Map usw.) leider häufig eine individuelle Behandlung zu erfordern.

Fazit: Es ist ein Chaos. Hoffen wir, dass es einmal eine native und saubere Klonfunktionalität geben wird.

flori
quelle
1
Dadurch werden statische Methoden nicht kopiert, da es sich nicht um aufzählbare eigene Eigenschaften handelt.
Herr Lavalamp
5
@ Mr.Lavalamp und wie kann man (auch) die statischen Methoden kopieren?
Flori
Dies wird die Arrays zerstören! Alle Arrays werden in Objekte mit den Tasten "0", "1", ... konvertiert.
Vahid
1
@KeshaAntonov Möglicherweise können Sie eine Lösung mit typeof- und Array-Methoden finden. Ich selbst habe es vorgezogen, alle Eigenschaften manuell zu klonen.
Vahid
1
Erwarten Sie nicht, dass Eigenschaften geklont werden
jduhls
9
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

Beachten Sie die Eigenschaften von Object.assign : Es wird eine flache Kopie erstellt und keine Klassenmethoden kopiert.

Wenn Sie eine tiefe Kopie oder mehr Kontrolle über die Kopie wünschen, gibt es die Funktionen des lodash-Klons .

Tom
quelle
2
Da Object.createerstellt neues Objekt mit angegebenen Prototyp, warum nicht dann einfach const clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah). Auch Klassenmethoden werden kopiert.
Barbatus
@barbatus Das verwendet allerdings den falschen Prototyp Blah.prototype != instanceOfBlah. Sie sollten verwendenObject.getPrototypeOf(instanceOfBlah)
Bergi
1
@Bergi Nein, die ES6-Klasseninstanz hat nicht immer einen Prototyp. Überprüfen Sie codepen.io/techniq/pen/qdZeZm, ob es auch mit der Instanz funktioniert.
Barbatus
@barbatus Sorry, was? Ich folge nicht. Alle Instanzen haben einen Prototyp, das macht sie zu Instanzen. Probieren Sie den Code aus Floris Antwort.
Bergi
1
@Bergi Ich denke es kommt auf die Babel Konfiguration an oder so. Ich implementiere gerade eine reaktive native App und Instanzen ohne geerbte Eigenschaften haben dort den Prototyp null. Wie Sie hier sehen können , ist es möglich, dass getPrototypeOf null zurückgibt. Developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/….
Barbatus
2

Es wird nicht empfohlen, Erweiterungen des Prototyps vorzunehmen. Dies führt zu Problemen, wenn Sie Tests an Ihrem Code / Ihren Komponenten durchführen. Die Unit-Test-Frameworks übernehmen nicht automatisch Ihre Prototyp-Erweiterungen. Es ist also keine gute Praxis. Weitere Erklärungen zu Prototypenerweiterungen finden Sie hier. Warum ist das Erweitern nativer Objekte eine schlechte Praxis?

Das Klonen von Objekten in JavaScript ist nicht einfach oder unkompliziert. Hier ist die erste Instanz, die "Shallow Copy" verwendet:

1 -> Flacher Klon:

class Employee {
    constructor(first, last, street) {
        this.firstName = first;
        this.lastName = last;
        this.address = { street: street };
    }

    logFullName() {
        console.log(this.firstName + ' ' + this.lastName);
    }
}

let original = new Employee('Cassio', 'Seffrin', 'Street A, 23');
let clone =  Object.assign({},original); //object.assing() method
let cloneWithPrototype Object.create(Object.getPrototypeOf(original)), original) //  the clone will inherit the prototype methods of the original.
let clone2 = { ...original }; // the same of object assign but shorter sintax using "spread operator"
clone.firstName = 'John';
clone.address.street = 'Street B, 99'; //will not be cloned

Ergebnisse:

original.logFullName ():

Ergebnis: Cassio Seffrin

clone.logFullName ():

Ergebnis: John Seffrin

original.address.street;

Ergebnis: 'Straße B, 99' // Beachten Sie, dass das ursprüngliche Unterobjekt geändert wurde

Hinweis: Wenn die Instanz Verschlüsse als eigene Eigenschaften hat, wird sie von dieser Methode nicht umbrochen. ( Lesen Sie mehr über Schließungen ) Außerdem wird das Unterobjekt "Adresse" nicht geklont.

clone.logFullName ()

wird nicht funktionieren.

cloneWithPrototype.logFullName ()

funktioniert, da der Klon auch seine Prototypen kopiert.

So klonen Sie Arrays mit Object.assign:

let cloneArr = array.map((a) => Object.assign({}, a));

Klonen Sie das Array mit ECMAScript Spread Sintax:

let cloneArrSpread = array.map((a) => ({ ...a }));

2 -> Deep Clone:

Um eine völlig neue Objektreferenz zu archivieren, können Sie JSON.stringify () verwenden, um das ursprüngliche Objekt als Zeichenfolge zu analysieren und es anschließend wieder in JSON.parse () zu analysieren.

let deepClone = JSON.parse(JSON.stringify(original));

Bei Deep Clone bleiben die Adressverweise erhalten. Die deepClone-Prototypen werden jedoch verloren gehen, daher funktioniert deepClone.logFullName () nicht.

3 -> Bibliotheken von Drittanbietern:

Eine weitere Option ist die Verwendung von Bibliotheken von Drittanbietern wie Loadash oder Unterstrich. Sie erstellen ein neues Objekt und kopieren jeden Wert vom Original in das neue Objekt, wobei die Referenzen im Speicher bleiben.

Unterstrich: let cloneUnderscore = _ (original) .clone ();

Loadash-Klon: var cloneLodash = _.cloneDeep (original);

Der Nachteil von lodash oder Unterstrich war die Notwendigkeit, einige zusätzliche Bibliotheken in Ihr Projekt aufzunehmen. Sie sind jedoch gute Optionen und führen auch zu Hochleistungsergebnissen.

Cassio Seffrin
quelle
1
Bei der Zuweisung an {}erbt der Klon keine der Prototypmethoden des Originals. clone.logFullName()wird überhaupt nicht funktionieren. Das, was Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal)du vorher hattest, war in Ordnung. Warum hast du das geändert?
Bergi
1
@Bergi danke für deinen Beitrag, ich habe gerade meine Antwort bearbeitet, ich habe deinen Punkt hinzugefügt, um die Prototypen zu kopieren!
Cassio Seffrin
1
Ich freue mich über Ihre Hilfe @Bergi, bitte lassen Sie Ihre Meinung jetzt. Ich habe die Ausgabe beendet. Ich denke jetzt hat die Antwort fast die ganze Frage abgedeckt. Danke!
Cassio Seffrin
1
Ja, und genau Object.assign({},original)so funktioniert es nicht.
Bergi
1
Manchmal ist der einfachere Ansatz alles, was wir brauchen. Wenn Sie keine Prototypen benötigen und komplexe Objekte möglicherweise nur "clone = {... original}" das Problem lösen könnten
Cassio Seffrin
0

Ein weiterer Liner:

Die meiste Zeit ... (funktioniert für Date, RegExp, Map, String, Number, Array), übrigens, Klonen von String, Nummer ist ein bisschen lustig.

let clone = new obj.constructor(...[obj].flat())

für Klassen ohne Kopierkonstruktor:

let clone = Object.assign(new obj.constructor(...[obj].flat()), obj)
Eric
quelle
0
class A {
  constructor() {
    this.x = 1;
  }

  y() {
    return 1;
  }
}

const a = new A();

const output = Object.getOwnPropertyNames(Object.getPrototypeOf(a)).concat(Object.getOwnPropertyNames(a)).reduce((accumulator, currentValue, currentIndex, array) => {
  accumulator[currentValue] = a[currentValue];
  return accumulator;
}, {});

Geben Sie hier die Bildbeschreibung ein

Alex Ivasyuv
quelle
-3

Sie können den Spread-Operator verwenden, wenn Sie beispielsweise ein Objekt mit dem Namen Obj klonen möchten:

let clone = { ...obj};

Und wenn Sie dem geklonten Objekt etwas ändern oder hinzufügen möchten:

let clone = { ...obj, change: "something" };
ttfreeman
quelle