Come clonare un'istanza di classe ES6 javascript


96

Come faccio a clonare un'istanza di classe Javascript utilizzando ES6.

Non mi interessano soluzioni basate su jquery o $ extent.

Ho visto discussioni piuttosto vecchie sulla clonazione di oggetti che suggeriscono che il problema è piuttosto complicato, ma con ES6 si presenta una soluzione molto semplice: la metterò di seguito e vedrò se le persone pensano che sia soddisfacente.

modifica: viene suggerito che la mia domanda è un duplicato; Ho visto quella risposta ma ha 7 anni e comporta risposte molto complicate utilizzando js pre-ES6. Sto suggerendo che la mia domanda, che consente ES6, ha una soluzione notevolmente più semplice.


2
Se hai una nuova risposta per una vecchia domanda su Stack Overflow, aggiungi quella risposta alla domanda originale, non crearne una nuova.
Scimmia eretica

1
Vedo il problema che Tom sta / stava affrontando poiché le istanze della classe ES6 funzionano in modo diverso dagli oggetti "normali".
CherryNerd

2
Inoltre, la prima parte di codice nella risposta accettata il tuo "possibile duplicato" fornisce effettivamente arresti anomali quando provo a eseguirlo su un'istanza di una classe ES6
CherryNerd

Penso che questo non sia un duplicato, perché sebbene l'istanza della classe ES6 sia un oggetto, non tutti gli oggetti sono istanze della classe ES6 e quindi l'altra domanda non risolve il problema di questa domanda.
Tomáš Zato - Ripristina Monica il

5
Non è un duplicato. L'altra domanda riguardava i puri Objectusati come detentori di dati. Questo riguarda ES6 classe il problema di non perdere le informazioni sul tipo di classe. Necessita di una soluzione diversa.
flori

Risposte:


111

È complicato; Ho provato molto! Alla fine, questa battuta ha funzionato per le mie istanze di classe ES6 personalizzate:

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

Evita di impostare il prototipo perché dicono che rallenti molto il codice.

Supporta i simboli ma non è perfetto per getter / setter e non funziona con proprietà non enumerabili (vedere la documentazione Object.assign () ). Inoltre, la clonazione di classi interne di base (come Array, Date, RegExp, Map, ecc.) Purtroppo sembra spesso richiedere una gestione individuale.

Conclusione: è un casino. Speriamo che un giorno ci sarà una funzionalità di clonazione nativa e pulita.


1
Questo non copierà i metodi statici perché non sono effettivamente proprietà enumerabili.
Mr. Lavalamp

5
@ Mr.Lavalamp e come puoi copiare (anche) i metodi statici?
flori

questo distruggerà gli array! Converte tutti gli array in oggetti con chiavi "0", "1", ...
Vahid

1
@KeshaAntonov Potresti riuscire a trovare una soluzione con i metodi typeof e Array. Io stesso ho preferito clonare manualmente tutte le proprietà.
Vahid

1
Non aspettarti che cloni proprietà che sono esse stesse oggetti: jsbin.com/qeziwetexu/edit?js,console
jduhls

10
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

Nota le caratteristiche di Object.assign : fa una copia superficiale e non copia i metodi della classe.

Se vuoi una copia completa o un maggiore controllo sulla copia, ci sono le funzioni di clonazione lodash .


2
Dal momento che Object.createcrea un nuovo oggetto con il prototipo specificato, perché non solo const clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah). Anche i metodi di classe verranno copiati.
barbatus

1
@barbatus che utilizza il prototipo sbagliato, però, Blah.prototype != instanceOfBlah. Dovresti usareObject.getPrototypeOf(instanceOfBlah)
Bergi

1
@Bergi no, l'istanza della classe ES6 non ha sempre un prototipo. Controlla codepen.io/techniq/pen/qdZeZm che funziona anche con l'istanza.
barbatus

@barbatus Scusa, cosa? Io non seguo Tutte le istanze hanno un prototipo, questo è ciò che le rende istanze. Prova il codice dalla risposta di flori.
Bergi

1
@Bergi Penso che dipenda dalla configurazione di Babel o qualcosa del genere. In questo momento sto implementando un'app nativa reattiva e le istanze senza proprietà ereditate hanno un prototipo nullo. Inoltre, come puoi vedere qui developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… è possibile che getPrototypeOf restituisca null.
barbatus

3

Non è consigliabile creare estensioni del prototipo, ciò comporterà problemi quando farai test sul tuo codice / componenti. I framework di unit test non assumeranno automaticamente le estensioni del tuo prototipo. Quindi non è una buona pratica. Ci sono più spiegazioni delle estensioni dei prototipi qui Perché estendere gli oggetti nativi è una cattiva pratica?

Per clonare oggetti in JavaScript non esiste un modo semplice o diretto. Ecco una prima istanza che utilizza "Shallow Copy":

1 -> Clone superficiale:

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

Risultati:

original.logFullName ():

risultato: Cassio Seffrin

clone.logFullName ():

risultato: John Seffrin

original.address.street;

risultato: "Street B, 99" // nota che l'oggetto secondario originale è stato modificato

Avviso: se l'istanza ha chiusure come proprietà proprie, questo metodo non la avvolgerà. ( leggi di più sulle chiusure ) Inoltre, il sottooggetto "indirizzo" non verrà clonato.

clone.logFullName ()

non funzionerà.

cloneWithPrototype.logFullName ()

funzionerà, perché il clone copierà anche i suoi prototipi.

Per clonare array con Object.assign:

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

Clona array utilizzando la sintassi di diffusione ECMAScript:

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

2 -> Deep Clone:

Per archiviare un riferimento a un oggetto completamente nuovo possiamo usare JSON.stringify () per analizzare l'oggetto originale come stringa e dopo analizzarlo di nuovo in JSON.parse ().

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

Con il clone profondo i riferimenti all'indirizzo verranno conservati. Tuttavia, i prototipi di deepClone verranno persi, quindi deepClone.logFullName () non funzionerà.

Biblioteche 3 -> 3 ° partito:

Un'altra opzione sarà l'uso di librerie di terze parti come loadash o underscore. Creeranno un nuovo oggetto e copieranno ogni valore dall'originale al nuovo oggetto mantenendo i suoi riferimenti in memoria.

Sottolineato: let cloneUnderscore = _ (originale) .clone ();

Clone di Loadash: var cloneLodash = _.cloneDeep (originale);

Lo svantaggio di lodash o di sottolineatura era la necessità di includere alcune librerie extra nel tuo progetto. Tuttavia sono buone opzioni e producono anche risultati ad alte prestazioni.


1
Quando si assegna a {}, il clone non erediterà nessuno dei metodi prototipo dell'originale. clone.logFullName()non funzionerà affatto. Quello Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal)che avevi prima andava bene, perché l'hai cambiato?
Bergi

1
@Bergi grazie per il tuo contributo, stavo modificando la mia risposta in questo momento, ho aggiunto il tuo punto per copiare i prototipi!
Cassio Seffrin

1
Apprezzo il tuo aiuto @Bergi, per favore lascia la tua opinione ora. Ho terminato l'edizione. Penso che ora la risposta abbia coperto quasi tutta la domanda. Grazie!
Cassio Seffrin

1
Sì, e proprio come Object.assign({},original), non funziona.
Bergi

1
a volte l'approccio più semplice è tutto ciò di cui abbiamo bisogno. Se non hai bisogno di prototipi e oggetti complessi potrebbe semplicemente "clone = {... original}" potrebbe risolvere il problema
Cassio Seffrin

0

Un altro rivestimento:

Il più delle volte ... (funziona per Data, RegExp, Mappa, Stringa, Numero, Matrice), btw, stringa di clonazione, numero è un po 'divertente.

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

per quelle classi senza costruttore di copia:

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

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;
}, {});

inserisci qui la descrizione dell'immagine


-4

Puoi usare l'operatore spread, ad esempio se vuoi clonare un oggetto chiamato Obj:

let clone = { ...obj};

E se vuoi cambiare o aggiungere qualcosa all'oggetto clonato:

let clone = { ...obj, change: "something" };
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.