Copiando objetos en Javascript
Copiando objetos en Javascript
Cuando tenemos que copiar objetos en Javascript disponemos de una serie de mecanismos para realizar dicha tarea.
Aunque en principio debería ser una tarea sencilla, la cosa se puede complicar un poco y debemos tener en cuenta ciertos factores.
Copia por referencia o por valor
En Javascript existen diferentes tipos de datos (Number, BigInt, Boolean, String, Null, Undefined, Symbol y Object) y estos tipos de datos los podríamos separar en dos grupos (primitivos y no primitivos).
En los tipos de datos primitivos cuando copiamos el contenido de una variable a otra, esa copia se ha realizado por valor, quedando de esta forma el nuevo valor desvinculado de la copia.
let x = 100; let y = x; // Copia por valor (se copia el contenido de la variable console.log(x); // 100 console.log(y); // 100 y = 200; console.log(x); // 100 console.log(y); // 200
El segundo grupo llamado no primitivos cuando realizamos esa misma copia, lo que estamos haciendo es copiar la referencia de memoria donde está almacenado el valor, por lo tanto estaríamos vinculados al valor original.
const objA = { name: 'AAAA', age: 20, }; const objB = objA; // En este punto lo que hicimos es copiar la referencia a la memoria donde esta almacenado el contenido console.log(objA); // {name: 'AAAA', age: 20} console.log(objB); // {name: 'AAAA', age: 20} // Cambiamos el valor del objeto, pero debemos recordar que hicimos una copia en "objB" de la referencia de "objA" por ese motivo "objA" ha cambiado. objB.name = 'BBBBBB'; console.log(objA); // {name: 'BBBBBB', age: 20} console.log(objB); // {name: 'BBBBBB', age: 20}
Bien, todo esto es necesario entenderlo ya que hemos podido ver que la copia por referencia nos puede provocar bastantes dolores de cabeza si no lo controlamos.
Formas de copiar un objeto
Podemos decir que el primer ejemplo y el mas sencillo, es asignar el valor de una variable a otra de tal forma que acabamos de guardar la referencia a la memoria donde está almacenado el valor.
const objA = { name: 'AAAA', age: 20, }; const objB = objA; console.log(objA); // {name: 'AAAA', age: 20} console.log(objB); // {name: 'AAAA', age: 20}
La siguiente forma de copiar objetos creo que es la mas conocida y se realiza usando el método Object.assign() y que además tiene su equivalente llamado spread operator desde la revisión ES6 / ES2015.
Con este método todas las propiedades que estén en el objeto destino y tengan la misma clave serán sobrescritas.
Otra cosa que debemos tener en cuenta es que la copia que se realiza usando este método, solo en el primer nivel del objeto realizará la copia nueva con sus respectiva referencia, pero en el resto de los niveles mantendrá la referencia del objeto copiado.
Este tipo de copia es conocida como shallow copy, pero también existe otro tipo de copia llamada deep copy.
A continuación una pequeña definición:
- shallow copy (copia superficial): Ciertos valores mas profundos seguirán vinculados a la variable original.
- deep copy (copia profunda): Todos los valores de la nueva variable se copian y se desvinculan de la variable original.
const objA = { name: 'AAAA', age: 20, data: { status: true } }; // Primer nivel estaría desvinculado const objB = Object.assign({}, objA); console.log(objA); // {name: 'AAAA', age: 20, data: {…}} console.log(objB); // {name: 'AAAA', age: 20, data: {…}} objB.name = 'BBBBB'; console.log(objA); // {name: 'AAAA', age: 20, data: {…}} console.log(objB); // {name: 'BBBBB', age: 20, data: {…}} // Siguientes niveles del objeto mantienen la misma referencia objB.data.status = false console.log(objA); // { name: 'AAAA', age: 20, data: {status: false} } console.log(objB); // { name: 'BBBBB', age: 20, data: {status: false} } // SPREAD OPERATOR (Mismo funcionamiento) const objAA = { a: 1, b: 2, c: { d: 3 } } const objBB = { ...objAA } console.log(objA); // {name: 'AAAA', age: 20, data: {…}} console.log(objB); // {name: 'BBBBB', age: 20, data: {…}} objBB.a = 100 console.log(objAA); // {a: 1, b: 2, c: {…}} console.log(objBB); // {a: 100, b: 2, c: {…}}
Otra forma de copiar objetos y de forma profundo (deep copy) es usando la serialización, ya que este proceso transforma el objeto a una cadena de texto eliminando cualquier referencia a la memoria del objeto original.
const A1 = { a: 1, b: { c: 2 } }; const B1 = JSON.stringify(A1); console.log(B1); // Tenemos el obejto en transformado a texto: '{"a":1,"b":{"c":2}}' const C1 = JSON.parse(B1); // Convertimos el texto a un objeto console.log(C1); // { a: 1, b: { c: 2 } } C1.a = 1000 C1.b.c = 20000 console.log(A1); // {a: 1, b: {c:2}} console.log(C1); // {a: 1000, b: {c:20000}}
Tiene algunos inconvenientes pero suele funcionar bastante bien y es muy rápida su ejecución.
El método estrella: structuredClone
Y por último tenemos el método estrella para realizar una copia profunda en Javascript sin usar ninguna librería externa o tener que desarrollar nosotros ningún algoritmo que haga ese trabajo.
Este nuevo método global es structuredClone() y corresponde a una mejora en la WEB API de los navegadores y se ha ido añadiendo poco a poco desde final del año 2021 y durante todo el año 2022.
A continuación veremos un ejemplo de cómo funciona.
const data = { a: 1, b: 2, c: { d: 3, e: 4, f: { g: 5, } } }; const copy = structuredClone(data); copy.c.f.g = 100000 console.log(data); console.log(copy);
Como hemos podido observar este último método es bastante util ya que junto a la serialización podemos tener una copia profunda y desvinculada del valor original.
Esta «nueva» función de Javascript facilita enormemente el código cuando trabajemos con objetos, que tratándose de javascript es siempre :).
Muy buen artículo, aunque yo en lugar de primarios y secundarios hablaría de objetos estructurados (como arrays, Date…) y no estructurados (string, number…).
Realmente la función structuredClone() soluciona el problema de clonar objetos estructurados, aunque funciona perfectamente con los otros.
Muchas gracias Juan por tu comentario.
Muy buen artículo Iván, completo y claro. enhorabuena.
Muchas gracias Jota!!!
Una vez aportando con este nuevo apunte, me ha gustado structuredClone(), lo usaré seguro.
Un abrazo amigo??
Muchas gracias por tus comentarios Juanma!!!, un saludo.