Cómo clonar una instancia de clase javascript ES6

96

¿Cómo clono una instancia de clase Javascript usando ES6?

No estoy interesado en soluciones basadas en jquery o $ extend.

He visto discusiones bastante antiguas sobre la clonación de objetos que sugieren que el problema es bastante complicado, pero con ES6 se presenta una solución muy simple: la pondré a continuación y veré si la gente piensa que es satisfactoria.

editar: se sugiere que mi pregunta es un duplicado; Vi esa respuesta, pero tiene 7 años e involucra respuestas muy complicadas usando js pre-ES6. Estoy sugiriendo que mi pregunta, que permite ES6, tiene una solución dramáticamente más simple.

Tom
fuente
2
Si tiene una nueva respuesta para una pregunta anterior en Stack Overflow, agregue esa respuesta a la pregunta original, no solo cree una nueva.
Heretic Monkey
1
Veo el problema al que se enfrenta Tom ya que las instancias de clase ES6 funcionan de manera diferente a los objetos "normales".
CherryNerd
2
Además, el primer fragmento de código en la respuesta aceptada que proporciona su "posible duplicado" en realidad se bloquea cuando intento ejecutarlo en una instancia de una clase ES6
CherryNerd
Creo que esto no es un duplicado, porque aunque la instancia de clase ES6 es un objeto, no todos los objetos son instancias de clase ES6 y, por lo tanto, la otra pregunta no aborda el problema de esta pregunta.
Tomáš Zato - Reincorpora a Monica
5
No es un duplicado. La otra pregunta se refería a los Objects puros utilizados como contenedores de datos. Este es sobre ES6 classes y el problema de no perder la información del tipo de clase. Necesita una solución diferente.
flori

Respuestas:

111

Es complicado; ¡Intenté mucho! Al final, esta frase funcionó para mis instancias de clase ES6 personalizadas:

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

Evita configurar el prototipo porque dicen que ralentiza mucho el código.

Admite símbolos, pero no es perfecto para captadores / definidores y no funciona con propiedades no enumerables (consulte los documentos de Object.assign () ). Además, la clonación de clases internas básicas (como Array, Date, RegExp, Map, etc.), lamentablemente, a menudo parece necesitar un manejo individual.

Conclusión: es un desastre. Esperemos que algún día haya una funcionalidad de clonación nativa y limpia.

flori
fuente
1
Esto no copiará los métodos estáticos porque en realidad no son propiedades propias enumerables.
Mr. Lavalamp
5
@ Mr.Lavalamp y ¿cómo se pueden copiar (también) los métodos estáticos?
flori
¡esto destruirá las matrices! Convertirá todas las matrices en objetos con "0", "1", ... teclas.
Vahid
1
@KeshaAntonov Es posible que pueda encontrar una solución con los métodos typeof y Array. Yo mismo preferí clonar todas las propiedades manualmente.
Vahid
1
No espere que clone propiedades que son en sí mismas objetos: jsbin.com/qeziwetexu/edit?js,console
jduhls
10
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

Tenga en cuenta las características de Object.assign : realiza una copia superficial y no copia métodos de clase.

Si desea una copia profunda o más control sobre la copia, existen las funciones de clonación lodash .

Tom
fuente
2
Dado que Object.createcrea un nuevo objeto con un prototipo especificado, ¿por qué no simplemente const clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah). También se copiarán los métodos de clase.
barbatus
1
@barbatus que utiliza el prototipo equivocado sin embargo, Blah.prototype != instanceOfBlah. Deberías usarObject.getPrototypeOf(instanceOfBlah)
Bergi
1
@Bergi no, la instancia de clase ES6 no siempre tiene un prototipo. Consulte codepen.io/techniq/pen/qdZeZm que también funciona con la instancia.
barbatus
@barbatus Lo siento, ¿qué? Yo no sigo. Todas las instancias tienen un prototipo, eso es lo que las convierte en instancias. Pruebe el código de la respuesta de Flori.
Bergi
1
@Bergi Creo que depende de la configuración de Babel o algo así. En este momento estoy implementando una aplicación nativa reactiva y las instancias sin propiedades heredadas tienen prototipo nulo allí. Además, como puede ver aquí, developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… es posible que getPrototypeOf devuelva nulo.
barbatus
3

No se recomienda hacer extensiones del prototipo, dará lugar a problemas cuando realice pruebas en su código / componentes. Los marcos de prueba unitarios no asumirán automáticamente sus extensiones de prototipo. Entonces no es una buena práctica. Hay más explicaciones sobre las extensiones de prototipos aquí. ¿Por qué es una mala práctica extender objetos nativos?

Para clonar objetos en JavaScript no existe una forma simple o directa. Aquí está la primera instancia que usa "Copia superficial":

1 -> Clon superficial:

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

Resultados:

original.logFullName ():

resultado: Cassio Seffrin

clone.logFullName ():

resultado: John Seffrin

original.address.street;

resultado: 'Calle B, 99' // observe que se cambió el subobjeto original

Aviso: si la instancia tiene cierres como propiedades propias, este método no la ajustará. ( lea más sobre cierres ) Y además, el subobjeto "dirección" no será clonado.

clone.logFullName ()

no trabajará.

cloneWithPrototype.logFullName ()

funcionará, porque el clon también copiará sus Prototipos.

Para clonar matrices con Object.assign:

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

Clonar matriz usando ECMAScript spread sintax:

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

2 -> Clon profundo:

Para archivar una referencia de objeto completamente nueva, podemos usar JSON.stringify () para analizar el objeto original como una cadena y luego volver a analizarlo en JSON.parse ().

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

Con la clonación profunda se mantendrán las referencias a la dirección. Sin embargo, los prototipos deepClone se perderán, por lo que deepClone.logFullName () no funcionará.

3 -> Bibliotecas de terceros:

Otras opciones serán utilizar bibliotecas de terceros como loadash o subrayado. Crearán un nuevo objeto y copiarán cada valor del original al nuevo objeto manteniendo sus referencias en la memoria.

Guión bajo: deje cloneUnderscore = _ (original) .clone ();

Clon de carga: var cloneLodash = _.cloneDeep (original);

La desventaja de lodash o subrayado fue la necesidad de incluir algunas bibliotecas adicionales en su proyecto. Sin embargo, son buenas opciones y también producen resultados de alto rendimiento.

Cassio Seffrin
fuente
1
Al asignar a {}, el clon no heredará ninguno de los métodos prototipo del original. clone.logFullName()no funcionará en absoluto. Lo Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal)que tenías antes estaba bien, ¿por qué lo cambiaste?
Bergi
1
@Bergi gracias por tu contribución, estaba editando mi respuesta en este momento, ¡agregué tu punto para copiar los prototipos!
Cassio Seffrin
1
Agradezco tu ayuda @Bergi, por favor deja tu opinión ahora. He terminado la edición. Creo que ahora la respuesta ha cubierto casi toda la pregunta. ¡Gracias!
Cassio Seffrin
1
Sí, y al igual Object.assign({},original)que, no funciona.
Bergi
1
a veces, el enfoque más simple es todo lo que necesitamos. Si no necesita prototipos y objetos complejos, simplemente "clone = {... original}" podría resolver el problema
Cassio Seffrin
0

Otro trazador de líneas:

La mayoría de las veces ... (funciona para Date, RegExp, Map, String, Number, Array), por cierto, la cadena de clonación, el número es un poco divertido.

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

para aquellas clases sin constructor de copia:

let clone = Object.assign(new obj.constructor(...[obj].flat()), obj)
Eric
fuente
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;
}, {});

ingrese la descripción de la imagen aquí

Alex Ivasyuv
fuente
-4

Puede usar el operador de propagación, por ejemplo, si desea clonar un objeto llamado Obj:

let clone = { ...obj};

Y si desea cambiar o agregar algo al objeto clonado:

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