Escuela Frontend
JavaScript

Aprende Correctamente a Clonar Objetos en JavaScript

Claudia Valdivieso
Autora
Claudia Valdivieso

Debido a que los objetos en JavaScript son pasados por referencia, no puedes simplemente copiar usando el =.

Pero no te preocupes, hoy aprenderemos 3 formas en que puedes clonar un objeto 😁

Los objetos son pasados por referencia

Tu primera pregunta podría ser, ¿por qué no puedo usar =?.

Veamos qué pasa si hacemos eso:

const objeto = { uno: 1, dos: 2 };
const objeto2 = objeto;
console.log(
objeto, // { uno: 1, dos: 2 };
objeto2, // { uno: 1, dos: 2 };
);

Hasta ahora, ambos objetos parecen dar la misma salida. Así que no hay problema, ¿verdad? Pero veamos qué pasa si editamos nuestro segundo objeto:

const objeto2.tres = 3;
console.log(objeto2);
// { uno: 1, dos: 2, tres: 3 }; <-- ✅
console.log(objeto);
// { uno: 1, dos: 2, tres: 3 }; <-- 😱

¡¿QUÉ?! Cambié objeto2 pero ¿por qué también se vio afectado objeto? 🤔

Eso es porque los objetos son pasados por referencia. Así que cuando usas =, se copia el puntero al espacio de memoria que ocupa.

Las referencias no contienen valores, son un puntero al valor en memoria.

Si quieres aprender más sobre esto, puedes revisar este artículo donde se da una explicación más detallada sobre ello.

1. Usando el operador Spread

Usando el operador spread se clonará tu objeto. Ten en cuenta que será una copia superficial.

const comida = { carne: '🥩', tocino: '🥓' };
const comidaClonada = { ...comida };
console.log(comidaClonada);
// { carne: '🥩', tocino: '🥓' }

2. Usando Object.assign

Podemos usar Object.assign que también creará una copia superficial del objeto.

const comida = { carne: '🥩', tocino: '🥓' };
const comidaClonada = Object.assign({}, comida);
console.log(comidaClonada);
// { carne: '🥩', tocino: '🥓' }

Nota el objeto vacío {} como primer argumento, esto asegurará que no mutes el objeto original 👍

3. Usando JSON

Esta forma es comúnmente muy recomendada ya que hará una copia profunda. Ahora, esta es una forma rápida y sucia de clonar un objeto en profundidad.

const comida = { carne: '🥩', tocino: '🥓' };
const comidaClonada = JSON.parse(JSON.stringify(comida));
console.log(comidaClonada);
// { carne: '🥩', tocino: '🥓' }

Esto funciona, pero tiene sus propios inconvenientes: sólo se clonan correctamente los tipos JSON válidos. Para una solución más robusta, yo recomendaría usar algo como lodash.

Lodash DeepClone vs JSON

Hay que tener en cuenta que hay algunas diferencias entre deepClone y JSON.stringify/parse.

  • JSON.stringify/parse sólo funciona con Number, String y Object literales sin propiedades de función o Símbolo.
  • deepClone trabaja con todos los tipos, la función y el Symbol se copian por referencia.

Aquí hay un ejemplo:

const lodashClonedeep = require('lodash.clonedeep');
const array = [
() => 2,
{ prueba: () => 3, },
Symbol('4'),
];
// deepClone copia por referencia la función y el Symbol
console.log(lodashClonedeep(array));
// [() => 2, { prueba: () => 3, }, Symbol('4')]
// JSON reemplaza la función y el Symbol por null y la función en el objeto por undefined
console.log(JSON.parse(JSON.stringify(array)));
// [ null, {}, null ]
// la función y el Symbol se copian por referencia en deepClone
console.log(
lodashClonedeep(array)[0] === lodashClonedeep(array)[0],
); // true
console.log(
lodashClonedeep(array)[2] === lodashClonedeep(array)[2],
); // true

Además, copiar usando JSON tiene problemas con las dependencias circulares y el orden de las propiedades en el objeto clonado puede ser diferente, pero si no estás usando nada más que JSON válido, este podría ser el enfoque más simple.

Clon superficial vs clon profundo

Cuando se utilizada spread ... para copiar un objeto, sólo estamos creando una copia superficial. Si el array está anidado o es multidimensional, no funcionará. Este es el ejemplo que vamos a utilizar:

const objetoAnidado = {
bandera: '🇵🇪',
pais: {
ciudad: 'lima',
},
};

Copia superficial

Vamos a clonar nuestro objeto usando spread:

const clonSuperficial = { ...objetoAnidado };
// Changed our cloned object
clonSuperficial.bandera = '🇺🇸';
clonSuperficial.pais.ciudad = 'washington';

Así que cambiamos nuestro objeto clonado cambiando la ciudad. Veamos el resultado.

console.log(clonSuperficial);
// {bandera: '🇺🇸', pais: {ciudad: 'washington'}}
console.log(objetoAnidado);
// {bandera: '🇵🇪', pais: {ciudad: 'washington'}} <-- 😱

Una copia superficial significa que se copia el primer nivel, los niveles más profundos se referencian.

Copia profunda

Tomemos el mismo ejemplo pero aplicando una copia profunda utilizando JSON

const clonSuperficial = JSON.parse(JSON.stringify(objetoAnidado));
clonSuperficial.bandera = '🇺🇸';
clonSuperficial.pais.ciudad = 'washington';
console.log(clonSuperficial);
// {bandera: '🇺🇸', pais: {ciudad: 'washington'}}
console.log(objetoAnidado);
// {bandera: '🇵🇪', pais: {ciudad: 'lima'}} <-- ✅

Como puedes ver, la copia profunda es una verdadera copia para los objetos anidados. Muchas veces la copia superficial es lo suficientemente buena, no se necesita realmente una copia profunda. Es como una pistola de clavos frente a un martillo. La mayoría de las veces el martillo está perfectamente bien. El uso de una pistola de clavos para algunas pequeñas artesanías es a menudo un caso de exageración, un martillo está muy bien. Se trata de utilizar la herramienta adecuada para el trabajo adecuado 🤓

Rendimiento

Probando el operador Spread vs Object.assign vs Lodash cloneDeep vs JSON, el resultado muestra que el operador Spread es mucho más rápido que todos los demás 🥇, pero ten en cuenta que solo hace una copia superficial! Viendo el lado de la copia profunda (Lodash vs JSON), Lodash tiene mejor performance 💯

Test de rendimiento

Otros Recursos

  • Puedes revisar este script que creó Chris Ferdinandi para clonar un objeto o array con vanilla JS, es muy parecido a lo que hace JSON pero con mejor rendimiento. Puedes ver la demo aquí.

Artículos Relacionados

¿Quieres mejorar tus habilidades de frontend?