Día 15 de 24: deepClone
15 de diciembre de 2025

Día 15 de 24: deepClone

Las 24 funciones antes de navidad

Por Asdrúbal Chirinos

Ayer seguimos afinando cómo controlar la frecuencia de ejecución y hoy damos un giro hacia algo que casi siempre aparece cuando empiezas a construir herramientas reales: copiar datos sin romper nada en el proceso. deepClone existe para que puedas duplicar estructuras complejas de forma segura, incluyendo arrays y objetos anidados, y además sobrevivir a referencias circulares sin explotar.

Día 14 de 24: throttle Las 24 funciones antes de navidad 14 de diciembre de 2025
Día 14 de 24: throttle

deepClone

Cuando trabajas con estado, payloads de APIs, configs, caches o estructuras que viajan entre capas, copiar por referencia es una receta para bugs invisibles. Modificas “la copia” y sin darte cuenta cambias el original.

deepClone resuelve ese problema: hace una clonación profunda de valores, copiando recursivamente objetos y arrays. Lo más útil es que maneja referencias circulares usando WeakMap, así que si tu estructura se apunta a sí misma (o comparte subárboles), la función devuelve un clon consistente en vez de entrar en recursión infinita.

En proyectos reales es común usarla para:

  • Evitar mutaciones accidentales cuando transformas datos.
  • Duplicar estado antes de aplicar cambios, sin tocar el original.
  • Trabajar con estructuras que vienen de librerías o SDKs y no quieres “ensuciar”.
  • Normalizar payloads antes de persistir o enviar.

Y a diferencia de hacer JSON.parse(JSON.stringify(obj)), aquí no pierdes Date o RegExp, y sí soportas ciclos.

Código de la función

/**
 * Realiza una clonación profunda de objetos, arrays y tipos primitivos.
 * Maneja referencias circulares usando WeakMap para rastreo.
 * No clona funciones, símbolos, ni tipos especiales (Date, RegExp, Map, Set se copian superficialmente).
 * @param {*} value Valor a clonar.
 * @param {WeakMap} [_cache] Cache interno para referencias circulares (uso interno).
 * @returns {*} Clon profundo del valor.
 */
export function deepClone(value, _cache = new WeakMap()) {
  // Primitivos y null
  if (value === null || typeof value !== 'object') return value;
  
  // Verificar cache para referencias circulares
  if (_cache.has(value)) return _cache.get(value);
  
  // Date
  if (value instanceof Date) return new Date(value);
  
  // RegExp
  if (value instanceof RegExp) return new RegExp(value.source, value.flags);
  
  // Array
  if (Array.isArray(value)) {
    const arrClone = [];
    _cache.set(value, arrClone);
    value.forEach((item, index) => {
      arrClone[index] = deepClone(item, _cache);
    });
    return arrClone;
  }
  
  // Map
  if (value instanceof Map) {
    const mapClone = new Map();
    _cache.set(value, mapClone);
    value.forEach((val, key) => {
      mapClone.set(key, deepClone(val, _cache));
    });
    return mapClone;
  }
  
  // Set
  if (value instanceof Set) {
    const setClone = new Set();
    _cache.set(value, setClone);
    value.forEach(item => {
      setClone.add(deepClone(item, _cache));
    });
    return setClone;
  }
  
  // Object plano
  const objClone = {};
  _cache.set(value, objClone);
  Object.keys(value).forEach(key => {
    objClone[key] = deepClone(value[key], _cache);
  });
  
  return objClone;
}

Cómo usarla

Caso básico

Clonar un objeto con anidación para poder modificar la copia sin tocar el original.

import { deepClone } from './deepClone.js';

const original = {
  user: { name: 'Astro', roles: ['admin'] },
  flags: { beta: true }
};

const copy = deepClone(original);
copy.user.name = 'Otro';
copy.user.roles.push('editor');

console.log(original.user.name);      // "Astro"
console.log(original.user.roles);     // ["admin"]
console.log(copy.user.roles);         // ["admin", "editor"]

Caso especial: referencias circulares

Estructuras que se apuntan a sí mismas suelen romper clones ingenuos. Aquí no.

const a = { name: 'A' };
a.self = a;

const a2 = deepClone(a);

console.log(a2 !== a);        // true
console.log(a2.self === a2);  // true

Caso especial: Map y Set

Funciona bien para clonar el contenedor y clonar profundamente los valores. En Map, las llaves se mantienen tal cual están (no se clonan).

const m = new Map();
m.set('config', { retries: 3 });

const s = new Set([{ id: 1 }, { id: 2 }]);

const m2 = deepClone(m);
const s2 = deepClone(s);

m2.get('config').retries = 10;
[...s2][0].id = 99;

console.log(m.get('config').retries);     // 3
console.log([...s][0].id);                // 1

Con deepClone ya tienes una herramienta pequeña pero poderosa para reducir bugs de mutación y manejar estructuras reales sin miedo a ciclos. Mañana seguimos con otra función lista para tu kit de utilidades, de esas que cuando la tienes, te preguntas cómo vivías sin ella.

Compartir:

¿Te gustó este artículo? Apoya mi trabajo y ayúdame a seguir creando contenido.

Cómprame un café