Páginas

viernes, 16 de junio de 2023

JavaScript: Como asegurarse de que es una Shallow copy.

La forma de asegurarnos que estamos haciendo una shallow copy es:

const myShallowCopy = JSON.parse(JSON.stringify(myObj));

con JSON.Stringify se convierte el objeto a copiar en una cadena de caracteres y con JSON.parse se vuelve a convertir en objeto. De esta manera estamos creando un nuevo objeto situado en otra referencia de memoria, es decir una shallow copy.

¿Por qué todo este jaleo? Depende de las necesidades del algoritmo a programar, nos interesa realizar deep copy (copia profunda) o shalow copy(copia superficial). Normalmente nos interesa copia superficial, de esta manera tenemos dos objetos independientes.

Diferencias entre deep y shallow copy.

Shallow copy copia un objeto en otra parte de la memoria (referencia). Entonces disponemos de dos objetos iguales que son independientes.

Deep copy copia la referencia al objeto copiado. Realmente es como si se tienen dos nombres para un mismo objeto, si se cambia algo en uno de ellos cambia automáticamente en el otro. Los dos objetos están apuntando a la misma referencia de memoria.

Ejemplo.

let obj = {a:1};
let shallowObj = {...obj};
shallowObj.a = 2;
console.log ("Shallow copy: obj "+JSON.stringify(obj)+" shallowObj "+ JSON.stringify(shallowObj));
// Shallow copy: obj {"a":1} shallowObj {"a":2}

let deepObj = obj;
deepObj.a = 3;
console.log (" Deep copy: obj "+JSON.stringify(obj)+" deepObj "+ JSON.stringify(deepObj));
// Deep copy: obj {"a":3} deepObj {"a":3}

La imagen muestra el comportamiento de shallow y deep copy. Cuando se modifica la shallow copy el objeto original no cambia, sin embargo sí cambia si se modifica la deep copy.

Otro Ejemplo más complejo.

Cuando un objeto contiene otro objeto en sus propiedades se produce una particularidad.

let obj = {a:1, b: {a:1}};
let shallowObj = {...obj};
shallowObj.a = 2;
shallowObj.b.a = 2;
console.log ("Shallow copy: obj "+JSON.stringify(obj) +" shallowObj "+ JSON.stringify(shallowObj));
// Shallow copy: obj {"a":1,"b":{"a":2}} shallowObj {"a":2,"b":{"a":2}}

obj = {a:1, b: {a:1}};
let deepObj = obj;
deepObj.a = 2;
deepObj.b.a = 2;
console.log (" Deep copy: obj "+JSON.stringify(obj)+" deepObj "+ JSON.stringify(deepObj));
// Deep copy: obj {"a":2,"b":{"a":2}} deepObj {"a":2,"b":{"a":2}}

La particularidad se produce en la Shallow copy, cuando se cambia el valor de la propiedad a del objeto almacenado en la propiedad b, es decir shallowObj1.b.a. Resulta que este objeto se ha copiado por referencia, o sea es una deep copy.

En este caso el problema es que el objeto b que está dentro del shallowObj en vez de copiarse como un nuevo objeto se ha copiado como una referencia al objeto dentro de la propiedad b de obj. Por eso cuando se cambia shallow.b.a resulta que estamos cambiando también obj.b.a.

Solución.

Esto puede ser inesperado para el programador que cuando cambia el valor en la copia también está cambiando el valor en el original. Una solución a esto es utilizar la sentencia mostrada al principio del artículo. Con stringify y parse nos aseguramos de que la shallow copy es total independientemente de los objetos contenidos en propiedades y los posibles anidamientos. Al hacer el parse se crean nuevos objetos, nunca copias por referencia. Por lo tanto los objetos son independientes y se pueden manejar sin interferir los valores de uno en los valores del otro.

Si quieres ver un video en el que explico los ejemplos, lo puedes hacer aqui