A ilusão da cópia rasa
Quando você copia um objeto com o spread ({ ...obj }) ou com
Object.assign({}, obj), o JavaScript copia apenas o primeiro nível. Se alguma
propriedade for, por sua vez, um objeto ou um array, o que se copia é a referência,
não o seu conteúdo. A cópia e o original compartilham esse objeto aninhado.
Pense em uma fotocópia de um álbum de fotos: a fotocópia tem a sua própria capa, mas em vez de copiar as fotos cola um post-it que diz "as fotos estão no álbum original". Se alguém riscar uma foto, ela aparece riscada em ambos.
const original = {
nome: "Ana",
endereco: { cidade: "Madri" },
};
const copia = { ...original };
copia.nome = "Bia"; // afeta SOMENTE a cópia (primeiro nível)
copia.endereco.cidade = "Bilbao"; // afeta TAMBÉM o original!
console.log(original.nome); // "Ana" (certo)
console.log(original.endereco.cidade); // "Bilbao" (bug!)
nome é uma string (primitivo) e foi copiado de verdade. Mas
endereco é um objeto: ambos o compartilham. Mudar um muda o outro.
Cópia profunda (deep copy)
Uma cópia profunda clona recursivamente todos os níveis, de modo que a cópia seja totalmente independente do original. Há três caminhos comuns:
1. structuredClone (a forma moderna)
É uma função nativa do navegador e do Node moderno que clona em profundidade:
const copia = structuredClone(original);
copia.endereco.cidade = "Bilbao";
console.log(original.endereco.cidade); // "Madri" (intacto)
Suporta objetos, arrays, Map, Set, datas... mas não funções.
2. JSON (para dados simples)
Converter para texto e voltar cria uma cópia profunda em uma linha:
const copia = JSON.parse(JSON.stringify(original));
É prático, mas só serve para dados "JSON-puros": perde funções,
undefined, datas (viram string), Map/Set, etc.
3. Recursão na mão
Percorrer a estrutura e clonar cada nível. É o que as duas opções anteriores fazem por dentro:
function clonar(valor) {
if (Array.isArray(valor)) return valor.map(clonar);
if (valor && typeof valor === "object") {
const saida = {};
for (const chave of Object.keys(valor)) saida[chave] = clonar(valor[chave]);
return saida;
}
return valor; // primitivos: retornados tal como estão
}
Congelar para impedir mutações: Object.freeze
Se você quiser garantir que um objeto não seja modificado, Object.freeze o
torna somente leitura: qualquer tentativa de mudança é ignorada (ou lança erro em
modo estrito).
const config = Object.freeze({ tema: "escuro" });
config.tema = "claro"; // ignorado
console.log(config.tema); // "escuro"
Cuidado:
Object.freezetambém é raso. Congela o primeiro nível, mas os objetos aninhados continuam mutáveis a menos que você os congele também (um "freeze profundo" recursivo).
Exemplos
O bug da cópia rasa
const original = { nivel: 1, dados: { pontos: [10, 20] } };
const copia = { ...original };
copia.dados.pontos.push(30); // muta o array COMPARTILHADO
console.log("original:", original.dados.pontos); // [10, 20, 30] contaminado!
console.log("copia: ", copia.dados.pontos); // [10, 20, 30]
Cópia profunda com structuredClone
const original = { nivel: 1, dados: { pontos: [10, 20] } };
const copia = structuredClone(original);
copia.dados.pontos.push(30);
console.log("original:", original.dados.pontos); // [10, 20] intacto
console.log("copia: ", copia.dados.pontos); // [10, 20, 30]
Object.freeze impede mutar
const ajustes = Object.freeze({ volume: 5 });
ajustes.volume = 11; // ignorado silenciosamente
console.log(ajustes.volume); // 5