En Javascript muchas veces, cuando estamos programando en JavaScript, es necesario copiar todo un objeto, ya sea porque necesitamos hacer modificaciones de sus valores pero conservando el objeto original, o porque no queremos enviar a algún método la referencia original.
Para esto, hay dos maneras de copiar un objeto y son deep y shallow copy, que básicamente vienen a ser copia profunda y copia superficial.
Contexto
Cuando creamos una variable en JavaScript, estas simplemente son referencias en memoria a donde realmente se encuentran los valores:
const v1 = {
uno:1,
dos:2
}
Si creamos otra variable de esta manera:
const v2 = v1;
Lo que estamos haciendo es solo crear una referencia a donde se encuentran los datos de v1 en v2, por lo tanto si modificamos v2 de esta manera:
v2.dos = 3;
console.log(v1.dos); // 3
console.log(v2.dos); // 3
Ahora tenemos el mismo valor en ambas variables por que no es una copia si no un apuntador o punteros como en el lenguaje C.
Shallow copy
Es una copia superficial, porque copia los valores de un objeto, pero si este objeto tiene objetos hijos, es decir, objetos anidados, estos no se copian y solo se toman las referencias, por lo que quedamos a la mitad entre una copia de valores y asignación por referencia.
El siguiente ejemplo detalla muy bien mi punto:
let original = {
a: 1,
b: {
c: 2
}
};
let shallowCopy = { ...original };
console.log(original.b.c); // Output: 2
console.log(shallowCopy.b.c); // Output: 2
// Modificamos el objeto interno en la copia
shallowCopy.b.c = 3;
console.log(original.b.c); // Output: 3
console.log(shallowCopy.b.c); // Output: 3
Como se ve si hay una copia, pero el objeto intento c es un apuntador.
Deep copy
Esto es una copia total de valores incluidos objetos hijos dentro del objeto padre y los hijos de los hijos, para ello se usa una función que tal vez sea muy conocida por todos.
Veamos un ejemplo:
let original = { a: 1, b: { c: 3 } };
let deepCopy = JSON.parse(JSON.stringify(original));
En este ejemplo, se realizaron dos pasos: el primero es convertir el objeto a un string y después, el string a objeto. Con este método, se crea una copia de los valores y dos referencias en memoria, y en caso de que se cambie algún valor, no afectará a ninguna variable.
Consideraciones
Este método tiene limitaciones, ya que sólo funcionará con propiedades y valores que puedan ser representados en JSON. No funcionará con funciones, expresiones regulares, y otros tipos de valores no serializables. Además, tampoco preserva la prototypical inheritance.