Clonado profundo con structuredClone

Intermedio
JavaScript
JavaScript
Actualizado: 19/04/2026

Qué es un deep clone

En JavaScript, al asignar un objeto a otra variable se copia la referencia, no el contenido. Cambiar el original afecta a la copia y viceversa. Cuando necesitamos un duplicado independiente de una estructura compleja hablamos de deep clone (clonado profundo).

const original = { user: { name: "Alan" } };
const copy = original;
copy.user.name = "Otro";

console.log(original.user.name); // "Otro" — era la misma referencia

Durante años el "truco" más común fue JSON.parse(JSON.stringify(x)). Funciona para objetos y arrays con datos básicos, pero falla con fechas, mapas, conjuntos, tipados binarios y cualquier referencia circular. Por eso ES2022 estandarizó structuredClone, una función global que utiliza el mismo algoritmo interno que usan los workers para pasar mensajes.

const state = { user: { name: "Alan" }, createdAt: new Date() };
const clone = structuredClone(state);

clone.user.name = "Otro";
console.log(state.user.name);      // "Alan" — independiente
console.log(clone.createdAt instanceof Date); // true

structuredClone está disponible en Node.js 17+ y en todos los navegadores modernos, sin importar nada.

Tipos que sí maneja

El algoritmo de clonado estructurado soporta una lista bastante extensa de tipos. Los más relevantes:

  • Primitivos: string, number, boolean, null, undefined, BigInt.
  • Objetos planos y arrays, incluidas propiedades con claves Symbol registradas.
  • Date (con la fecha exacta).
  • RegExp (fuente y flags).
  • Map y Set, clonando claves y valores en profundidad.
  • ArrayBuffer, TypedArray y DataView.
  • Blob, File, FileList (en navegadores).
  • Cualquier combinación anidada de los anteriores y referencias circulares.

Date, Map, Set y RegExp

Estos son los casos en los que JSON.parse(JSON.stringify(...)) falla de forma clara:

const data = {
  createdAt: new Date("2026-01-15T10:00:00Z"),
  tags: new Set(["js", "ts", "node"]),
  scores: new Map([["alan", 10], ["noa", 9]]),
  pattern: /^\d{3}-\d{2}$/
};

const jsonCopy = JSON.parse(JSON.stringify(data));
console.log(jsonCopy.createdAt instanceof Date); // false (pasa a string)
console.log(jsonCopy.tags instanceof Set);       // false (queda {})
console.log(jsonCopy.pattern instanceof RegExp); // false (queda {})

const sc = structuredClone(data);
console.log(sc.createdAt instanceof Date); // true
console.log(sc.tags);                      // Set(3) { 'js', 'ts', 'node' }
console.log(sc.scores.get("alan"));        // 10
console.log(sc.pattern.test("123-45"));    // true

Tipos binarios

Los buffers se clonan byte a byte, lo que permite duplicar bloques de datos de forma eficiente:

const buffer = new Uint8Array([1, 2, 3, 4]);
const copy = structuredClone(buffer);

copy[0] = 99;
console.log(buffer[0]); // 1 — copia independiente

Referencias circulares

Una estructura autorreferencial hace saltar un error a JSON.stringify, pero structuredClone la replica sin problema:

const node = { label: "root" };
node.self = node;
node.children = [node];

const clone = structuredClone(node);
console.log(clone === clone.self);         // true
console.log(clone.children[0] === clone);  // true
console.log(clone !== node);               // true — son estructuras distintas

El algoritmo usa una tabla interna para recordar qué objetos ya ha clonado, lo que le permite respetar la identidad dentro del clon.

Tipos que NO se clonan

No todo se puede copiar. structuredClone lanza un DataCloneError cuando encuentra:

  • Funciones, tanto funciones propias como métodos. Si una clase usa métodos en el prototipo, el clon seguirá funcionando porque el prototipo no se copia, pero las funciones almacenadas como valores de propiedad no son clonables.
  • Prototipos personalizados. El clon es un objeto plano con los mismos datos; pierde la relación con tu clase. No hay instanceof MiClase después de clonar.
  • Nodos del DOM, Window, HTMLElement, Event, etc.
  • Cualquier objeto "host" no soportado (p. ej. handles de Node).

Ejemplo del problema con clases:

class User {
  constructor(name) { this.name = name; }
  greet() { return `Hola, ${this.name}`; }
}

const u = new User("Alan");
const clone = structuredClone(u);

console.log(clone.name);            // "Alan"
console.log(clone instanceof User); // false
// console.log(clone.greet());      // TypeError: clone.greet is not a function

Si necesitas que el clon sea de la misma clase, combina structuredClone con una reconstrucción explícita:

const data = structuredClone({ name: u.name });
const cloneAsUser = new User(data.name);

Y el caso clásico del function member:

const obj = { onClick: () => console.log("click") };
// structuredClone(obj); // DataCloneError: () => {} could not be cloned

Opción transfer: mover en lugar de copiar

Algunos tipos binarios admiten ser transferidos en lugar de copiados. Tras la transferencia, el original queda vacío pero no se duplica memoria. Es útil con buffers grandes:

const buffer = new ArrayBuffer(1024 * 1024); // 1 MB
const clone = structuredClone({ data: buffer }, { transfer: [buffer] });

console.log(buffer.byteLength);           // 0  (ya no pertenece al original)
console.log(clone.data.byteLength);       // 1048576

Después de la transferencia, cualquier acceso al buffer original está detached. Úsalo solo cuando puedas garantizar que el emisor ya no necesita el recurso.

Comparación con JSON.parse(JSON.stringify)

| Característica | JSON.parse(JSON.stringify(x)) | structuredClone(x) | |----------------|------------------------------------|------------------------| | Objetos y arrays sencillos | Sí | Sí | | Date | No (se convierte a string ISO) | Sí | | Map / Set | No (se pierden) | Sí | | RegExp | No | Sí | | ArrayBuffer / TypedArray | No | Sí | | Referencias circulares | Lanza error | Sí | | Funciones | Se descartan | DataCloneError | | Prototipo personalizado | Se pierde | Se pierde | | Rendimiento | Generalmente más lento | Optimizado nativamente | | Disponibilidad | Siempre | Node 17+ y navegadores modernos |

Para casi cualquier deep clone nuevo, structuredClone es la opción recomendada. Solo se recurre a JSON en entornos muy antiguos o cuando se necesita además serializar a cadena para enviar por red.

Casos prácticos

Estado inmutable en gestores de estado

Una forma muy habitual de aplicar actualizaciones inmutables es clonar, mutar la copia y reasignar:

function setUserName(state, name) {
  const draft = structuredClone(state);
  draft.user.name = name;
  return draft;
}

const next = setUserName({ user: { name: "Alan" } }, "Noa");
console.log(next.user.name); // "Noa"

Es más claro que componer a mano el nuevo árbol con spread, especialmente cuando hay varios niveles.

Snapshot para deshacer (undo)

Para implementar un historial simple, basta con guardar clones del estado relevante:

const history = [];

function snapshot(state) {
  history.push(structuredClone(state));
}

function undo() {
  history.pop();
  return history[history.length - 1];
}

Aislar datos externos

Cuando recibes datos de una librería y quieres trabajar con una copia propia para no mutar estructuras ajenas, un structuredClone al principio es la forma más directa.

Buenas prácticas

  • Prefiere structuredClone sobre JSON.parse(JSON.stringify(...)) si el entorno es moderno (Node 17+, navegadores actuales). Es más correcto y soporta más tipos.
  • Recuerda que el clon pierde el prototipo: si necesitas conservar la clase, reconstruye manualmente la instancia.
  • No intentes clonar objetos con funciones como propiedades. Sepáralos: guarda los datos en un objeto plano y las funciones aparte.
  • Para clonar solo una parte de un objeto grande, construye antes el subobjeto mínimo; el algoritmo recorre todo lo que le pases.
  • Usa transfer únicamente con buffers que estés seguro de no volver a necesitar.
  • Si la estructura es sencilla y el rendimiento es crítico, pruébalo siempre: en algunos motores un clon manual con spread puede ser más rápido.

Resumen

structuredClone moderniza el deep clone en JavaScript. Con una sola llamada obtenemos copias independientes de estructuras que incluyen Date, Map, Set, RegExp, ArrayBuffer y referencias circulares, sin el coste ni las pérdidas de información de JSON.parse(JSON.stringify(...)). Sus limitaciones son conocidas y razonables: no clona funciones, prototipos ni nodos DOM. Integrado en Node 17+ y en todos los navegadores recientes, es la opción estándar para copia profunda en código nuevo.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, JavaScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de JavaScript

Explora más contenido relacionado con JavaScript y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Entender qué es un deep clone y por qué se necesita.
  • Usar structuredClone para duplicar estructuras complejas.
  • Clonar tipos que se pierden con JSON: Date, Map, Set, TypedArray, RegExp, ArrayBuffer.
  • Manejar referencias circulares sin caer en bucles infinitos.
  • Conocer las limitaciones: funciones, prototipos personalizados, nodos DOM.
  • Transferir recursos con la opción transfer para evitar copias innecesarias.

Cursos que incluyen esta lección

Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje