W3docs

Referencias y Copia de Objetos en JavaScript

Aprende cómo JavaScript almacena y copia objetos por referencia, cómo clonar con Object.assign y spread, y cómo hacer una copia profunda con structuredClone().

Una de las distinciones más importantes en JavaScript es cómo trata los valores primitivos frente a los objetos. Los primitivos se copian "como un valor completo", pero los objetos se almacenan y copian "por referencia". Malentender esta única regla es el origen de innumerables errores, por eso esta guía explica exactamente qué ocurre en memoria, cómo comparar y clonar objetos, y cómo copiarlos de forma segura.

Los Primitivos se Copian por Valor

Un primitivo — un string, número, boolean, null, undefined, bigint o symbol — se copia como un valor completo e independiente. Asignar una variable a otra duplica el valor, por lo que ambas variables quedan completamente separadas a partir de ese momento.

let message = "Hello";
let phrase = message; // a full copy of the string is made

phrase = "Goodbye";

console.log(message); // "Hello"  — unaffected
console.log(phrase);  // "Goodbye"

Cambiar phrase no tiene ningún efecto sobre message. Hay dos strings independientes en memoria.

Los Objetos se Almacenan y Copian por Referencia

Los objetos funcionan de manera diferente. Una variable asignada a un object no contiene el object en sí — contiene una referencia (un puntero) a donde vive el object en memoria. Copiar esa variable copia la referencia, no el object. Ambas variables apuntan entonces al mismo object.

javascript— editable

Sigue habiendo un solo object. Simplemente tenemos dos variables — user y admin — que ambas lo referencian. Un cambio realizado a través de cualquiera de ellas es visible a través de la otra, porque describen la misma cosa.

Comparación por Referencia

Dos variables de object son iguales con == o === solo cuando referencian el mismo object. Dos objetos independientes nunca son iguales, aunque su contenido parezca idéntico.

javascript— editable

Esto es intencional: === para objetos pregunta "¿son estos el mismo object?", no "¿tienen estos objetos los mismos datos?". Comparar contenidos requiere un enfoque diferente (a menudo comparando propiedades una a una, o serializando con JSON).

Los Objetos const Aún Pueden Mutarse

Una sorpresa habitual: un object declarado con const todavía puede tener sus propiedades modificadas. const congela el enlace — la variable nunca puede reasignarse a un valor diferente — pero no congela el contenido del object.

const user = { name: 'John' };

user.name = 'Pete'; // OK — we are mutating the object, not reassigning the variable
console.log(user.name); // 'Pete'

// user = { name: 'Alice' }; // TypeError: Assignment to constant variable

El primer cambio funciona porque user sigue referenciando el mismo object. La reasignación falla porque apuntaría user a un object completamente nuevo, lo cual const prohíbe. (Para congelar también el contenido, usa Object.freeze().)

Clonación Superficial

¿Y si realmente quieres una copia separada de un object? Tienes que crear un nuevo object y copiar las propiedades existentes en él. Dos formas modernas y concisas de hacerlo son Object.assign y la sintaxis spread.

Object.assign(target, ...sources) copia todas las propiedades propias enumerables de los sources en el target y devuelve el target:

javascript— editable

La sintaxis spread {...obj} hace lo mismo de una forma aún más breve (ver parámetros rest y sintaxis spread):

let user = { name: 'John', age: 30 };

let clone = { ...user };       // a shallow copy
let merged = { ...user, age: 31 }; // copy, then override age

console.log(clone);  // { name: 'John', age: 30 }
console.log(merged); // { name: 'John', age: 31 }

Ambas técnicas producen una copia real e independiente — pero solo en el nivel superior. Ese matiz importa, y nos lleva a la siguiente sección.

El Problema de los Objetos Anidados

Una copia superficial duplica las propiedades del nivel superior. Pero si el valor de una propiedad es en sí mismo un object o un array, solo se copia la referencia a ese object anidado — no el object anidado en sí. El clon y el original por tanto comparten el mismo object anidado.

javascript— editable

Aunque clone es un object de nivel superior separado, clone.sizes y user.sizes apuntan al mismo object anidado. Mutarlo a través de cualquiera de los dos caminos afecta a ambos. Este es exactamente el tipo de error de referencia compartida accidental que sorprende a quienes asumen que una copia con spread es "totalmente independiente".

Advertencia

Spread ({...obj}) y Object.assign solo copian un nivel de profundidad. Si tu object contiene objetos o arrays anidados, la copia comparte esos valores anidados con el original — mutar una propiedad anidada a través de uno cambiará silenciosamente el otro. Para datos anidados, usa una copia profunda.

Clonación Profunda con structuredClone()

Para copiar un object y todo lo que hay anidado dentro de él, usa el built-in structuredClone(). Clona recursivamente los objetos y arrays anidados, por lo que el resultado es completamente independiente del original.

javascript— editable

structuredClone también maneja las referencias circulares (un object que, directa o indirectamente, se refiere a sí mismo) sin fallar:

let user = {};
user.self = user; // user references itself

let clone = structuredClone(user);

console.log(clone === clone.self); // true — the cycle is preserved correctly

Además de objetos y arrays simples, admite muchos tipos built-in, incluyendo Date, Map, Set, RegExp, ArrayBuffer y typed arrays — copiando sus valores fielmente en lugar de convertirlos en otra cosa.

Limitaciones de structuredClone

structuredClone es potente pero no universal:

  • No puede clonar funciones — intentarlo lanza un DataCloneError. Lo mismo aplica a los nodos DOM.
  • No copia propiedades con clave symbol, getters/setters de propiedades, ni la cadena de prototipos del object — simplemente se eliminan del resultado.
// This throws DataCloneError because functions can't be cloned:
// structuredClone({ run: () => {} });

Si necesitas copiar funciones o instancias de clase con sus métodos, structuredClone no es la herramienta adecuada — tendrías que escribir un clon personalizado, o usar una librería como cloneDeep de lodash.

El Viejo Truco de JSON

Antes de que structuredClone estuviera ampliamente disponible, un hack popular para la copia profunda era serializar un object a un string JSON y parsearlo de nuevo:

let user = { name: 'John', sizes: { height: 182, width: 50 } };

let clone = JSON.parse(JSON.stringify(user)); // deep copy via JSON

clone.sizes.width = 60;
console.log(user.sizes.width); // 50 — original unaffected

Funciona para datos simples y seguros en JSON, pero tiene inconvenientes reales:

  • Las funciones y undefined se pierden — las claves que los contienen simplemente desaparecen.
  • Los objetos Date se convierten en stringsJSON.stringify convierte una fecha en un string ISO, y el parseo no la restaura.
  • Map, Set y otros tipos especiales se pierden o se convierten en objetos vacíos.
  • Las referencias circulares lanzan un TypeError.
Información

Para la clonación profunda, prefiere structuredClone() al truco de JSON.parse(JSON.stringify(...)). El enfoque JSON pierde silenciosamente funciones, undefined y tipos especiales, deforma las fechas, y falla con referencias circulares — structuredClone maneja todo eso correctamente.

Una Nota sobre la Recolección de Basura

Una vez que empiezas a copiar referencias, vale la pena recordar cómo JavaScript recupera la memoria. Un object permanece en memoria solo mientras sea alcanzable — mientras alguna variable, propiedad o entrada de array lo referencie. Cuando desaparece la última referencia a un object, se vuelve inalcanzable y es elegible para la recolección de basura. Nunca liberas objetos manualmente; el motor lo hace automáticamente.

Caso Real: Copiar Estado Antes de Mutarlo

Esto no es académico. En el código moderno de interfaces de usuario (React, Redux y similares) la regla estándar es "nunca mutes el estado directamente — produce una nueva copia con el cambio aplicado". Copiar superficialmente con spread es la herramienta del día a día para esto:

javascript— editable

Observa que hacemos spread tanto del object de nivel superior como del array cart anidado. Como spread es superficial, debes copiar explícitamente cada nivel anidado que pretendas modificar — de lo contrario caes directamente de vuelta en la trampa de la referencia compartida descrita anteriormente. Cuando los datos están profundamente anidados y necesitas una copia completa y segura, recurre a structuredClone.

Resumen

  • Los primitivos se copian por valor — las copias son totalmente independientes.
  • Los objetos se almacenan y copian por referencia — copiar la variable copia el puntero, por lo que ambas variables comparten un mismo object.
  • === compara objetos por referencia: solo el mismo object es igual a sí mismo.
  • const fija el enlace, no el contenido — los objetos const aún pueden mutarse.
  • Object.assign({}, obj) y {...obj} hacen copias superficiales que comparten objetos anidados.
  • structuredClone(obj) hace una copia profunda, maneja referencias circulares y muchos tipos built-in, pero no puede clonar funciones ni nodos DOM y elimina claves symbol, getters y prototipos.
  • Prefiere structuredClone al truco con pérdidas de JSON.parse(JSON.stringify(...)).

Pon a Prueba tu Conocimiento

Práctica
Después de let a = {}; let b = a; b.x = 1; — ¿cuál es el valor de a.x?
Después de let a = {}; let b = a; b.x = 1; — ¿cuál es el valor de a.x?
Práctica
Dado let c = { n: 1 }; let d = { n: 1 }; — ¿a qué se evalúa c === d?
Dado let c = { n: 1 }; let d = { n: 1 }; — ¿a qué se evalúa c === d?
Práctica
¿Cuál afirmación sobre la copia de objetos en JavaScript es correcta?
¿Cuál afirmación sobre la copia de objetos en JavaScript es correcta?
Was this page helpful?