Deep Clone de Objetos en JavaScript: Mas alla de JSON.parse
Compara estrategias de deep clone incluyendo JSON.parse, structuredClone, recursion manual y librerias para copiar objetos anidados con referencias circulares
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Deep Clone de Objetos en JavaScript: Mas alla de JSON.parse
Copia objetos JavaScript anidados sin referencias compartidas usando enfoques modernos y legacy. Esta recipe compara JSON.parse, structuredClone, clonado recursivo manual y soluciones con librerias mientras maneja casos edge como referencias circulares, funciones y tipos especiales de objetos.
Cuando Usar Esto
- El manejo de estado requiere actualizaciones inmutables sin mutar datos originales
- Las respuestas de API son cacheadas y no deben ser modificadas por consumidores
- Los objetos de configuracion se pasan a multiples modulos que pueden modificarlos
Solucion
1. Enfoque JSON.parse (Limitado)
// clones/JsonClone.ts
function jsonClone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}
// Funciona para objetos plain y arrays
const original = { a: 1, b: { c: 2 } };
const copy = jsonClone(original);
// Limitaciones
jsonClone({ date: new Date() }); // Date se convierte en string
jsonClone({ map: new Map() }); // Map se convierte en {}
jsonClone({ fn: () => 1 }); // Function se convierte en undefined
jsonClone({ a: {} }); copy.a = original; // Circular: throw
2. structuredClone (Browsers Modernos y Node 17+)
// clones/StructuredClone.ts
function modernClone<T>(obj: T): T {
return structuredClone(obj);
}
// Soporta mas tipos
const original = {
date: new Date(),
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
arrayBuffer: new Uint8Array([1, 2, 3]).buffer,
nested: { a: 1 },
};
const copy = modernClone(original);
// Limitaciones
modernClone({ fn: () => 1 }); // Function throw
modernClone({ el: document.body }); // DOM nodes throw
3. Clonado Recursivo Manual
// clones/RecursiveClone.ts
function deepClone<T>(obj: T, cache = new WeakMap<object, unknown>()): T {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (cache.has(obj)) {
return cache.get(obj) as T;
}
if (obj instanceof Date) {
return new Date(obj.getTime()) as unknown as T;
}
if (Array.isArray(obj)) {
const copy: unknown[] = [];
cache.set(obj, copy);
obj.forEach((item, index) => {
copy[index] = deepClone(item, cache);
});
return copy as unknown as T;
}
const copy = Object.create(Object.getPrototypeOf(obj));
cache.set(obj, copy);
Object.entries(obj as Record<string, unknown>).forEach(([key, value]) => {
copy[key] = deepClone(value, cache);
});
return copy;
}
4. Clonado con Librerias
// clones/LibraryClone.ts
import cloneDeep from 'lodash/cloneDeep';
import { klona } from 'klona';
const lodashCopy = cloneDeep(original);
const klonaCopy = klona(original);
const obj = {
date: new Date(),
regex: /test/gi,
nested: { a: 1 },
};
lodashCopy.nested.a = 2; // obj.nested.a sigue siendo 1
klonaCopy.nested.a = 3; // obj.nested.a sigue siendo 1
5. Comparacion de Rendimiento
// benchmarks/cloneBench.ts
const largeObject = {
users: Array.from({ length: 1000 }, (_, i) => ({
id: i,
name: `User ${i}`,
metadata: { created: new Date(), tags: ['a', 'b'] },
})),
};
// Resultados para 1000 iteraciones (aproximados):
// JSON.parse: ~50ms (mas rapido pero limitado)
// structuredClone: ~80ms (nativo, sin funciones)
// klona: ~120ms (compacto, moderno)
// lodash: ~200ms (mas robusto)
// recursive: ~250ms (customizable)
Como Funciona
- JSON.parse serializa a string y luego parsea, eliminando tipos no-JSON
- structuredClone es una API nativa que soporta mas tipos pero excluye funciones
- Clonado recursivo atraviesa propiedades, preservando prototype chains y manejando refs circulares
- Librerias optimizan hot paths y manejan casos edge como descriptors y symbols
Consideraciones de Produccion
- Usa
structuredCloneen ambientes modernos para rendimiento nativo - Prefiere
klonasobrelodashsi importa el tamano del bundle - Para estado de React, considera Immer para structural sharing en lugar de clonado completo
Errores Comunes
- Usar
JSON.parsepara objetos que contienen Dates, Maps o funciones - Hacer spread de objetos anidados (
{ ...obj }) que solo hace shallow-clone del primer nivel - No manejar referencias circulares, causando stack overflow en soluciones recursivas
FAQ
P: Es const copy = { ...original } un deep clone?
R: No. Crea un shallow copy. Los objetos anidados siguen siendo referencias compartidas.
P: Puedo hacer deep clone de instancias de clases?
R: structuredClone elimina metodos. Usa recursion manual o librerias que preservan prototypes.