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 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: structuredClose

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

deep-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.