Día 15 de 24: deepClone
Las 24 funciones antes de navidad
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.
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.
¿Qué opinas sobre este post?
Comentar en XCompartir:
¿Te gustó este artículo? Apoya este blog y ayuda a que siga creciendo.
Invítame un café