DevPath · Aprende a programar ESPTEN

Programación funcional

Copia superficial vs profunda (shallow vs deep)

El espejismo de la copia superficial

Cuando copias un objeto con el spread ({ ...obj }) o con Object.assign({}, obj), JavaScript copia solo el primer nivel. Si alguna propiedad es a su vez un objeto o un array, lo que se copia es la referencia, no su contenido. La copia y el original comparten ese objeto anidado.

Piensa en una fotocopia de un álbum de fotos: la fotocopia tiene su propia portada, pero en lugar de copiar las fotos pega un post-it que dice "las fotos están en el álbum original". Si alguien tacha una foto, aparece tachada en ambos.

const original = {
  nombre: "Ana",
  direccion: { ciudad: "Madrid" },
};

const copia = { ...original };
copia.nombre = "Bea";              // afecta SOLO a la copia (primer nivel)
copia.direccion.ciudad = "Bilbao"; // ¡afecta TAMBIÉN al original!

console.log(original.nombre);          // "Ana"   (bien)
console.log(original.direccion.ciudad); // "Bilbao" (¡bug!)

El nombre es un string (primitivo) y se copió de verdad. Pero direccion es un objeto: ambos lo comparten. Cambiar uno cambia el otro.

Copia profunda (deep copy)

Una copia profunda clona recursivamente todos los niveles, de modo que la copia es totalmente independiente del original. Hay tres caminos habituales:

1. structuredClone (la forma moderna)

Es una función nativa del navegador y de Node moderno que clona en profundidad:

const copia = structuredClone(original);
copia.direccion.ciudad = "Bilbao";
console.log(original.direccion.ciudad); // "Madrid" (intacto)

Soporta objetos, arrays, Map, Set, fechas... pero no funciones.

2. JSON (para datos simples)

Convertir a texto y volver crea una copia profunda en una línea:

const copia = JSON.parse(JSON.stringify(original));

Es práctico, pero solo sirve para datos "JSON-puros": pierde funciones, undefined, fechas (se vuelven string), Map/Set, etc.

3. Recursión a mano

Recorrer la estructura y clonar cada nivel. Es lo que hacen las dos opciones anteriores por dentro:

function clonar(valor) {
  if (Array.isArray(valor)) return valor.map(clonar);
  if (valor && typeof valor === "object") {
    const salida = {};
    for (const clave of Object.keys(valor)) salida[clave] = clonar(valor[clave]);
    return salida;
  }
  return valor; // primitivos: se devuelven tal cual
}

Congelar para impedir mutaciones: Object.freeze

Si quieres garantizar que un objeto no se modifique, Object.freeze lo vuelve de solo lectura: cualquier intento de cambio se ignora (o lanza error en modo estricto).

const config = Object.freeze({ tema: "oscuro" });
config.tema = "claro";      // ignorado
console.log(config.tema);   // "oscuro"

Cuidado: Object.freeze también es superficial. Congela el primer nivel, pero los objetos anidados siguen siendo mutables salvo que los congeles tú también (un "freeze profundo" recursivo).

Ejemplos

El bug de la copia superficial

const original = { nivel: 1, datos: { puntos: [10, 20] } };
const copia = { ...original };
copia.datos.puntos.push(30); // muta el array COMPARTIDO
console.log("original:", original.datos.puntos); // [10, 20, 30] ¡contaminado!
console.log("copia:   ", copia.datos.puntos);    // [10, 20, 30]

Copia profunda con structuredClone

const original = { nivel: 1, datos: { puntos: [10, 20] } };
const copia = structuredClone(original);
copia.datos.puntos.push(30);
console.log("original:", original.datos.puntos); // [10, 20] intacto
console.log("copia:   ", copia.datos.puntos);    // [10, 20, 30]

Object.freeze impide mutar

const ajustes = Object.freeze({ volumen: 5 });
ajustes.volumen = 11; // ignorado silenciosamente
console.log(ajustes.volumen); // 5
Pon esto en práctica

DevPath es un curso práctico: aquí lees la teoría; en la app la pones en práctica con ejercicios que se ejecutan de verdad, sin conexión.

Empezar gratis en la app →
← Composición y curryingVer el módulo →