¿Es esta una buena manera de clonar un objeto en ES6?

155

Buscar en Google "objeto de clonación de JavaScript" trae algunos resultados realmente extraños, algunos de ellos están irremediablemente desactualizados y otros son demasiado complejos, ¿no es tan fácil como:

let clone = {...original};

¿Hay algo malo con esto?

Dmitry Fadeev
fuente
1
Esto no es legal ES6. Pero si no fuera así, esto no es un clon: tanto su clon como sus propiedades originales apuntan a las mismas cosas ahora. Por ejemplo, original = { a: [1,2,3] }te da un clon con clone.aser literalmente original.a. Modificación a través de cloneo originalmodifica la misma cosa , así que no, esto es malo =)
Mike 'Pomax' Kamermans
2
@AlbertoRivera Es JavaScript un poco válido, en el sentido de que es una propuesta de etapa 2 que probablemente será una futura adición al estándar de JavaScript.
Frxstrem
@Frxstrem con la pregunta sobre ES6, esto no es válido JavaScript =)
Mike 'Pomax' Kamermans
3
¿Clonación superficial o profunda?
Felix Kling
2
Tienes razón, no es ES6 válido, es ES9 válido . developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
mikemaccana

Respuestas:

240

Esto es bueno para la clonación superficial . La propagación del objeto es una parte estándar de ECMAScript 2018 .

Para una clonación profunda, necesitará una solución diferente .

const clone = {...original} clon superficial

const newobj = {...original, prop: newOne} para agregar de manera inmutable otro accesorio al original y almacenarlo como un nuevo objeto.

Mark Shust en M. academia
fuente
18
Sin embargo, ¿no es esto solo un clon superficial? Como en, las propiedades no se clonan recursivamente, ¿verdad? Por lo tanto, original.innerObject === clone.innerObject y cambiando original.innerObject.property cambiará clone.innerObject.property.
milanio
18
Sí, este es un clon superficial. si quieres un clon profundo debes usarJSON.parse(JSON.stringify(input))
Mark Shust en M.academy
8
/! \ JSON.parse (JSON.stringify (input)) desordena las fechas, indefinidas, ... ¡No es la bala de plata para la clonación! Ver: maxpou.fr/immutability-js-without-library
Guillaume
1
Entonces, ¿el truco JSON.stringify () / JSON.parse () es realmente la forma recomendada de clonar en profundidad un objeto en ES6? Lo sigo viendo recomendado. Perturbador.
Solvitieg
3
@MarkShust JSON.parse(JSON.stringify(input))no funcionará, porque si existe functionso infinitycomo valores, simplemente se asignará nullen su lugar. Solo funcionará si los valores son simples literalsy no functions.
barra invertidaN
65

EDITAR: cuando se publicó esta respuesta, la {...obj}sintaxis no estaba disponible en la mayoría de los navegadores. Hoy en día, deberías estar bien usándolo (a menos que necesites soportar 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 }

Sin embargo, esto no hará un clon profundo. Todavía no existe una forma nativa de clonación profunda.

EDITAR: Como @Mike 'Pomax' Kamermans mencionó en los comentarios, puede clonar objetos simples (es decir, sin prototipos, funciones o referencias circulares) usando JSON.parse(JSON.stringify(input))

Alberto Rivera
fuente
19
Hay uno, siempre que su objeto sea un verdadero objeto literal, y puramente datos, en cuyo caso JSON.parse(JSON.stringify(input))es un clon profundo adecuado. Sin embargo, en el momento en que los prototipos, funciones o referencias circulares están en juego, esa solución ya no funciona.
Mike 'Pomax' Kamermans
@ Mike'Pomax'Kamermans Eso es cierto. Sin embargo, perder la funcionalidad para captadores y colocadores es terrible ...
Alberto Rivera
Si necesita una función genérica para clonar en profundidad cualquier objeto, consulte stackoverflow.com/a/13333781/560114 .
Matt Browne
1
Ahora hay una manera de hacer una clonación profunda de forma nativa .
Dan Dascalescu
1
@DanDascalescu, aunque es experimental, parece bastante prometedor. Gracias por la info!
Alberto Rivera
4

Si los métodos que usó no funcionan bien con objetos que involucran tipos de datos como Fecha , intente esto

Importar _

import * as _ from 'lodash';

Objeto clon profundo

myObjCopy = _.cloneDeep(myObj);
Shaheer Shukur
fuente
Solo import _ from 'lodash';es suficiente. Pero +1 para la respuesta "no reinventar la rueda".
rustyx
Lodash está hinchado. Realmente no hay necesidad de sacar lodash solo por una simple copia profunda. Muchas otras soluciones aquí. Esta es una respuesta realmente mala para los desarrolladores web que buscan crear una aplicación esbelta.
Jason Rice
3

si no desea utilizar json.parse (json.stringify (objeto)), puede crear copias recursivamente de valor clave:

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;
}

Pero la mejor manera es crear una clase que pueda devolver un clon de sí mismo

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
fuente
2

Siguiendo con la respuesta de @marcel, encontré que todavía faltaban algunas funciones en el objeto clonado. p.ej

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

donde en MyObject pude clonar el método A pero se excluyó el método B. Esto ocurrió porque falta

enumerable: true

lo que significaba que no apareció en

for(let key in item)

En cambio, cambié a

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

que incluirá claves no enumerables.

También descubrí que el prototipo ( proto ) no estaba clonado. Para eso terminé usando

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

PD: Frustrante que no pude encontrar una función integrada para hacer esto.

Shane Gannon
fuente
1

Puedes hacerlo así también,

let copiedData = JSON.parse(JSON.stringify(data));
rafee_que_
fuente
-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.

Pero Object.assign () no crea un clon 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 solucionarlo, deberíamos usar el ciclo de clonación que examina cada valor de usuario [clave] y, si es un objeto, replicar también su estructura. Eso se llama una "clonación profunda".

Hay un algoritmo estándar para la clonación profunda que maneja el caso anterior y casos más complejos, llamado algoritmo de clonación estructurada . Para no reinventar la rueda, podemos usar una implementación funcional desde la biblioteca de JavaScript, ya que el método se llama _.cloneDeep (obj) .

Mohamed Elshahawy
fuente
-1

Todos los métodos anteriores no manejan la clonación profunda de objetos donde está anidada en n niveles. No verifiqué su rendimiento sobre otros, pero es breve y simple.

El primer ejemplo a continuación muestra la clonación de objetos usando Object.assignqué clones hasta el primer nivel.

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 el siguiente enfoque clones profundos objeto

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
fuente
JSON.parse / stringify ha sido mencionado como un método pobre de clonación profunda durante años . Por favor, consulte las respuestas anteriores, así como las preguntas relacionadas. Además, esto no es nuevo para ES6.
Dan Dascalescu
@DanDascalescu Lo sé y creo que no debería ser un problema usarlo para objetos simples. Otros también han mencionado esto en sus respuestas en la misma publicación e incluso como comentarios. Creo que no merece un voto negativo.
Saksham
Exactamente - "otros también han mencionado" JSON.parse / stringify en sus respuestas. ¿Por qué publicar otra respuesta con la misma solución?
Dan Dascalescu