Essa é uma boa maneira de clonar um objeto no ES6?

155

A pesquisa no "objeto clone javascript" traz alguns resultados realmente estranhos, alguns deles estão irremediavelmente desatualizados e outros são muito complexos, não é tão fácil quanto:

let clone = {...original};

Há algo de errado nisso?

Dmitry Fadeev
fonte
1
isso não é legal ES6. Mas se não fosse, isso não é um clone: ​​tanto o seu clone quanto as propriedades originais apontam para as mesmas coisas agora. Por exemplo, original = { a: [1,2,3] }dá a você um clone do clone.aser literal original.a. Modificação, quer através de cloneou originalmodifica a mesma coisa , de modo nenhum, isso é ruim =)
Mike 'Pomax' Kamermans
2
@AlbertoRivera É um JavaScript meio válido, pois é uma proposta de estágio 2 que provavelmente será uma adição futura ao padrão JavaScript.
Frxstrem 28/09
@Frxstrem com a pergunta sobre ES6, isso não é válido JavaScript =)
Mike 'Pomax' Kamermans
3
Clonagem superficial ou profunda?
Felix Kling
2
Você está certo, não é ES6 válido, é ES9 válido . developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
mikemaccana

Respostas:

240

Isso é bom para clonagem superficial . A propagação do objeto é uma parte padrão do ECMAScript 2018 .

Para clonagem profunda, você precisará de uma solução diferente .

const clone = {...original} clone raso

const newobj = {...original, prop: newOne} para adicionar de forma imutável outro suporte ao original e armazenar como um novo objeto.

Mark Shust na M.academy
fonte
18
No entanto, isso não é apenas um clone superficial? Como em, as propriedades não são clonadas recursivamente, são? Portanto, original.innerObject === clone.innerObject e a alteração de original.innerObject.property alterará clone.innerObject.property.
Milanio 3/04
18
Sim, este é um clone superficial. se você quer um clone profundo, você deve usarJSON.parse(JSON.stringify(input))
Mark Shust em M.academy 04/04
8
/! \ JSON.parse (JSON.stringify (input)) atrapalha datas, indefinidas, ... Não é a bala de prata para a clonagem! Veja: maxpou.fr/immutability-js-without-library
Guillaume
1
Então, o hack JSON.stringify () / JSON.parse () é realmente a maneira recomendada de clonar profundamente um objeto no ES6? Eu continuo vendo isso recomendado. Perturbador.
Solvitieg
3
O @MarkShust JSON.parse(JSON.stringify(input))não funcionará, porque se houver functionsou infinitycomo valores, ele simplesmente atribuirá nullem seu lugar. Só funcionará se os valores forem simples literalse não functions.
backslashN
65

EDIT: Quando esta resposta foi publicada, a {...obj}sintaxe não estava disponível na maioria dos navegadores. Atualmente, você deve estar bem em usá-lo (a menos que precise dar suporte ao IE 11).

Use Object.assign.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

var obj = { a: 1 };
var copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }

No entanto, isso não fará um clone profundo. Ainda não existe uma maneira nativa de clonagem profunda.

EDIT: Como o @Mike 'Pomax' Kamermans mencionado nos comentários, você pode clonar profundamente objetos simples (por exemplo, sem protótipos, funções ou referências circulares) usando JSON.parse(JSON.stringify(input))

Alberto Rivera
fonte
19
Existe uma, desde que seu objeto seja um objeto literal literal e dados puramente, nesse caso, JSON.parse(JSON.stringify(input))é um clone profundo adequado. No entanto, no momento em que protótipos, funções ou referências circulares estão em jogo, essa solução não funciona mais.
Mike 'Pomax' Kamermans
@ Mike'Pomax'Kamermans Isso é verdade. Perder a funcionalidade de getters e setters é terrível, no entanto ...
Alberto Rivera
Se você precisar de uma função genérica para clonar profundamente qualquer objeto, consulte stackoverflow.com/a/13333781/560114 .
precisa saber é o seguinte
1
Agora existe uma maneira de fazer clonagem profunda nativamente .
Dan Dascalescu
1
@ DanDascalescu, embora seja experimental, parece bastante promissor. Obrigado pela informação!
Alberto Rivera
4

Se os métodos utilizados não estiverem funcionando bem com objetos que envolvem tipos de dados como Data , tente este

Importar _

import * as _ from 'lodash';

Objeto de clone profundo

myObjCopy = _.cloneDeep(myObj);
shaheer shukur
fonte
Apenas import _ from 'lodash';é suficiente. Mas +1 na resposta "não reinvente a roda".
Rustyx 19/09/19
lodash está inchado. Realmente não há necessidade de puxar o lodash apenas para uma cópia simples e profunda. Muitas outras soluções aqui. Essa é uma resposta muito ruim para desenvolvedores da Web que desejam criar um aplicativo lean.
26619 Jason Rice
3

se você não quiser usar o json.parse (json.stringify (object)), poderá criar cópias recursivas de valores-chave:

function copy(item){
  let result = null;
  if(!item) return result;
  if(Array.isArray(item)){
    result = [];
    item.forEach(element=>{
      result.push(copy(element));
    });
  }
  else if(item instanceof Object && !(item instanceof Function)){ 
    result = {};
    for(let key in item){
      if(key){
        result[key] = copy(item[key]);
      }
    }
  }
  return result || item;
}

Mas a melhor maneira é criar uma classe que possa retornar um clone dele mesmo

class MyClass{
    data = null;
    constructor(values){ this.data = values }
    toString(){ console.log("MyClass: "+this.data.toString(;) }
    remove(id){ this.data = data.filter(d=>d.id!==id) }
    clone(){ return new MyClass(this.data) }
}
marcel
fonte
2

Seguindo a resposta de @marcel, descobri que algumas funções ainda estavam ausentes no objeto clonado. por exemplo

function MyObject() {
  var methodAValue = null,
      methodBValue = null

  Object.defineProperty(this, "methodA", {
    get: function() { return methodAValue; },
    set: function(value) {
      methodAValue = value || {};
    },
    enumerable: true
  });

  Object.defineProperty(this, "methodB", {
    get: function() { return methodAValue; },
    set: function(value) {
      methodAValue = value || {};
    }
  });
}

onde no MyObject eu poderia clonar methodA, mas o methodB foi excluído. Isso ocorreu porque está faltando

enumerable: true

o que significava que não apareceu em

for(let key in item)

Em vez disso, mudei para

Object.getOwnPropertyNames(item).forEach((key) => {
    ....
  });

que incluirá chaves não enumeráveis.

Também descobri que o protótipo ( proto ) não era clonado. Por isso acabei usando

if (obj.__proto__) {
  copy.__proto__ = Object.assign(Object.create(Object.getPrototypeOf(obj)), obj);
}

PS: Frustrante não conseguir encontrar uma função incorporada para fazer isso.

Shane Gannon
fonte
1

Você pode fazer assim também,

let copiedData = JSON.parse(JSON.stringify(data));
rafee_que_
fonte
-1
We can do that with two way:
1- First create a new object and replicate the structure of the existing one by iterating 
 over its properties and copying them on the primitive level.

let user = {
     name: "John",
     age: 30
    };

    let clone = {}; // the new empty object

    // let's copy all user properties into it
    for (let key in user) {
      clone[key] = user[key];
    }

    // now clone is a fully independant clone
    clone.name = "Pete"; // changed the data in it

    alert( user.name ); // still John in the original object

2- Second we can use the method Object.assign for that 
    let user = { name: "John" };
    let permissions1 = { canView: true };
    let permissions2 = { canEdit: true };

    // copies all properties from permissions1 and permissions2 into user
    Object.assign(user, permissions1, permissions2);

  -Another example

    let user = {
      name: "John",
      age: 30
    };

    let clone = Object.assign({}, user);
It copies all properties of user into the empty object and returns it. Actually, the same as the loop, but shorter.

Mas Object.assign () não cria um clone profundo

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

let clone = Object.assign({}, user);

alert( user.sizes === clone.sizes ); // true, same object

// user and clone share sizes
user.sizes.width++;       // change a property from one place
alert(clone.sizes.width); // 51, see the result from the other one

Para consertar isso, devemos usar o loop de clonagem que examina cada valor do usuário [chave] e, se for um objeto, replicar sua estrutura também. Isso é chamado de "clonagem profunda".

Existe um algoritmo padrão para clonagem profunda que lida com o caso acima e casos mais complexos, chamado algoritmo de clonagem estruturada . Para não reinventar a roda, podemos usar uma implementação funcional da biblioteca JavaScript lodash, o método é chamado _.cloneDeep (obj) .

Mohamed Elshahawy
fonte
-1

Todos os métodos acima não tratam de clonagem profunda de objetos nos quais está aninhado em n níveis. Não verifiquei seu desempenho em relação aos outros, mas é curto e simples.

O primeiro exemplo abaixo mostra a clonagem de objetos usando Object.assignquais clones até o primeiro nível.

var person = {
    name:'saksham',
    age:22,
    skills: {
        lang:'javascript',
        experience:5
    }
}

newPerson = Object.assign({},person);
newPerson.skills.lang = 'angular';
console.log(newPerson.skills.lang); //logs Angular

Usando o objeto de clones profundos da abordagem abaixo

var person = {
    name:'saksham',
    age:22,
    skills: {
        lang:'javascript',
        experience:5
    }
}

anotherNewPerson = JSON.parse(JSON.stringify(person));
anotherNewPerson.skills.lang = 'angular';
console.log(person.skills.lang); //logs javascript

Saksham
fonte
JSON.parse / stringify foi mencionado como um método pobre de clonagem profunda por anos . Verifique as respostas anteriores, bem como perguntas relacionadas. Além disso, isso não é novidade no ES6.
Dan Dascalescu
@ DanDascalescu Eu sei disso e acho que não deve ser um problema usá-lo para objetos simples. Outros também mencionaram isso em suas respostas no mesmo post e até como comentários. Eu acho que não merece um voto negativo.
Saksham
Exatamente - "outros também mencionaram" JSON.parse / stringify em suas respostas. Por que postar mais uma resposta com a mesma solução?
Dan Dascalescu