Como clonar uma instância de classe javascript ES6

102

Como faço para clonar uma instância de classe Javascript usando ES6.

Não estou interessado em soluções baseadas em jquery ou $ extend.

Já vi discussões bastante antigas sobre clonagem de objetos que sugerem que o problema é bastante complicado, mas com o ES6 uma solução muito simples se apresenta - vou colocá-la abaixo e ver se as pessoas acham que é satisfatória.

editar: está sendo sugerido que minha pergunta é uma duplicata; Eu vi essa resposta, mas ela tem 7 anos e envolve respostas muito complicadas usando js pré-ES6. Estou sugerindo que minha pergunta, que permite o ES6, tem uma solução muito mais simples.

Tom
fonte
2
Se você tem uma nova resposta para uma pergunta antiga no Stack Overflow, adicione essa resposta à pergunta original, não apenas crie uma nova.
Macaco herege
1
Eu vejo o problema que Tom está / estava enfrentando, uma vez que as instâncias da classe ES6 funcionam de maneira diferente dos objetos "normais".
CherryNerd
2
Além disso, o primeiro trecho de código na resposta aceita que sua "possível duplicata" fornece na verdade trava quando tento executá-lo em uma instância de uma classe ES6
CherryNerd
Acho que não é uma duplicata, porque embora a instância da classe ES6 seja um objeto, nem todo objeto é uma instância da classe ES6 e, portanto, a outra questão não aborda o problema desta questão.
Tomáš Zato - Reintegrar Monica
5
Não é uma duplicata. A outra pergunta era sobre Objects puros usados ​​como suportes de dados. Este é sobre ES6 classes e o problema de não perder a informação do tipo de aula. Precisa de uma solução diferente.
flori,

Respostas:

117

É complicado; Eu tentei muito! No final, esse one-liner funcionou para minhas instâncias de classe ES6 personalizadas:

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

Isso evita definir o protótipo porque eles dizem que retarda muito o código.

Ele suporta símbolos, mas não é perfeito para getters / setters e não funciona com propriedades não enumeráveis ​​(consulte a documentação Object.assign () ). Além disso, a clonagem de classes internas básicas (como Array, Date, RegExp, Map, etc.), infelizmente, muitas vezes parece precisar de algum tratamento individual.

Conclusão: é uma bagunça. Esperemos que um dia haja uma funcionalidade de clone nativa e limpa.

flori
fonte
1
Isso não copiará métodos estáticos porque eles não são propriedades próprias enumeráveis.
Sr. Lavalamp
5
@ Mr.Lavalamp e como você pode copiar (também) os métodos estáticos?
flori
isso vai destruir os arrays! Ele irá converter todos os arrays em objetos com "0", "1", ... chaves.
Vahid
1
@KeshaAntonov Você pode encontrar uma solução com os métodos typeof e Array. Eu mesmo preferia clonar todas as propriedades manualmente.
Vahid
1
Não espere que ele clone propriedades que são objetos: jsbin.com/qeziwetexu/edit?js,console
jduhls
12
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

Observe as características de Object.assign : ele faz uma cópia superficial e não copia métodos de classe.

Se você deseja uma cópia profunda ou mais controle sobre a cópia, existem as funções de clone do lodash .

Tom
fonte
2
Uma vez que Object.createcria um novo objeto com o protótipo especificado, por que não apenas const clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah). Além disso, os métodos de classe também serão copiados.
barbatus de
1
@barbatus Mas que usa o protótipo errado Blah.prototype != instanceOfBlah,. Você deve usarObject.getPrototypeOf(instanceOfBlah)
Bergi
1
@Bergi não, a instância da classe ES6 nem sempre tem um protótipo. Verifique codepen.io/techniq/pen/qdZeZm se ele também funciona com a instância.
barbatus,
@barbatus Desculpe, o quê? Eu não sigo. Todas as instâncias têm um protótipo, é isso que as torna instâncias. Experimente o código da resposta de Flori.
Bergi,
1
@Bergi acho que depende da configuração do Babel ou algo assim. No momento, estou implementando um aplicativo nativo reativo e as instâncias sem propriedades herdadas têm protótipo nulo ali. Além disso, como você pode ver aqui, developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… é possível que getPrototypeOf retorne nulo.
barbatus,
4

Não é recomendado fazer extensões do Protótipo, isso vai resultar em problemas quando você fizer testes em seu código / componentes. Os frameworks de teste de unidade não assumirão automaticamente suas extensões de protótipo. Portanto, não é uma boa prática. Há mais explicações sobre extensões de protótipo aqui. Por que estender objetos nativos é uma prática ruim?

Para clonar objetos em JavaScript, não existe uma maneira simples ou direta. Aqui está a primeira instância usando "Cópia superficial":

1 -> Clone raso:

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: 'Street B, 99' // observe que o subobjeto original foi alterado

Aviso: Se a instância tiver fechamentos como propriedades próprias, este método não irá envolvê-la. ( leia mais sobre fechamentos ) E mais, o subobjeto "endereço" não será clonado.

clone.logFullName ()

não funciona.

cloneWithPrototype.logFullName ()

funcionará, porque o clone também copiará seus protótipos.

Para clonar matrizes com Object.assign:

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

Array clone usando a sintaxe de propagação ECMAScript:

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

2 -> Clone Profundo:

Para arquivar uma referência de objeto completamente nova, podemos usar JSON.stringify () para analisar o objeto original como string e depois analisá-lo de volta para JSON.parse ().

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

Com o clone profundo, as referências ao endereço serão mantidas. No entanto, os Protótipos deepClone serão perdidos, portanto, o deepClone.logFullName () não funcionará.

3 -> Bibliotecas de terceiros:

Outra opção será usar bibliotecas de terceiros, como loadash ou sublinhado. Eles irão criar um novo objeto e copiar cada valor do original para o novo objeto mantendo suas referências na memória.

Sublinhado: deixe cloneUnderscore = _ (original) .clone ();

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

A desvantagem do lodash ou do sublinhado foi a necessidade de incluir algumas bibliotecas extras em seu projeto. Porém são boas opções e também produzem resultados de alto desempenho.

Cassio Seffrin
fonte
1
Ao atribuir a {}, o clone não herdará nenhum dos métodos de protótipo do original. clone.logFullName()não funcionará de todo. O que Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal)você tinha antes estava bom, por que você mudou isso?
Bergi
1
@Bergi agradece sua contribuição, eu estava editando a minha resposta agora, adicionei seu ponto de copiar os protótipos!
Cassio Seffrin
1
Agradeço sua ajuda @Bergi, por favor, deixe sua opinião agora. Eu terminei a edição. Acho que agora a resposta cobriu quase todas as questões. Thks!
Cassio Seffrin
1
Sim, e assim como Object.assign({},original)não funciona.
Bergi
1
às vezes, a abordagem mais simples é tudo de que precisamos. Se você não precisa de protótipos e objetos complexos, "clone = {... original}" pode resolver o problema
Cassio Seffrin
0

Outro forro:

Na maioria das vezes ... (funciona para Date, RegExp, Map, String, Number, Array), btw, string de clonagem, número é um pouco engraçado.

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

para aquelas classes sem construtor de cópia:

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

insira a descrição da imagem aqui

Alex Ivasyuv
fonte
-1

Gosto de quase todas as respostas. Tive esse problema e para resolvê-lo faria manualmente definindo um clone()método e dentro dele, construiria todo o objeto do zero. Para mim, isso faz sentido porque o objeto resultante será naturalmente do mesmo tipo que o objeto clonado.

Exemplo com texto datilografado:

export default class ClassName {
    private name: string;
    private anotherVariable: string;
   
    constructor(name: string, anotherVariable: string) {
        this.name = name;
        this.anotherVariable = anotherVariable;
    }

    public clone(): ClassName {
        return new ClassName(this.name, this.anotherVariable);
    }
}

Eu gosto desta solução porque parece mais 'Orientada a Objetos'

Hassan Ali Salem
fonte
-3

Você pode usar o operador spread, por exemplo, se quiser clonar um objeto chamado Obj:

let clone = { ...obj};

E se você quiser alterar ou adicionar algo ao objeto clonado:

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