Referências e Cópia de Objetos em JavaScript
Entenda como objetos JavaScript são armazenados e copiados por referência, como clonar com Object.assign e spread, e como fazer deep clone com structuredClone().
Uma das distinções mais importantes em JavaScript é como a linguagem trata valores primitivos versus objetos. Primitivos são copiados "como um valor completo", mas objetos são armazenados e copiados "por referência". Compreender mal essa única regra é a origem de inúmeros bugs, por isso este guia explica exatamente o que acontece na memória, como comparar e clonar objetos, e como copiá-los com segurança.
Primitivos São Copiados por Valor
Um primitivo — uma string, número, boolean, null, undefined, bigint ou símbolo — é copiado como um valor completo e independente. Atribuir uma variável a outra duplica o valor, de modo que as duas variáveis ficam completamente separadas em seguida.
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"Alterar phrase não tem efeito algum em message. Existem duas strings independentes na memória.
Objetos São Armazenados e Copiados por Referência
Objetos funcionam de forma diferente. Uma variável atribuída a um object não armazena o object em si — ela armazena uma referência (um ponteiro) para onde o object vive na memória. Copiar essa variável copia a referência, não o object. Ambas as variáveis passam a apontar para o mesmo object.
Ainda existe apenas um object. Temos apenas duas variáveis — user e admin — que ambas o referenciam. Uma alteração feita através de qualquer uma delas é visível pela outra, porque ambas descrevem a mesma coisa.
Comparação por Referência
Duas variáveis de object são iguais com == ou === apenas quando referenciam o mesmo object. Dois objetos independentes nunca são iguais, mesmo quando seus conteúdos parecem idênticos.
Isso é intencional: === para objetos pergunta "esses dois são o mesmo object?", não "esses objetos têm os mesmos dados?". Comparar conteúdos requer uma abordagem diferente (geralmente comparando propriedades uma a uma, ou serializando com JSON).
Objetos const Ainda Podem Ser Mutados
Uma surpresa comum: um object declarado com const ainda pode ter suas propriedades alteradas. const congela a ligação — a variável nunca pode ser reatribuída a um valor diferente — mas não congela o conteúdo do 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 variableA primeira alteração funciona porque user ainda referencia o mesmo object. A reatribuição falha porque apontaria user para um object completamente novo, o que const proíbe. (Para congelar também o conteúdo, use Object.freeze().)
Clonagem Superficial
E se você genuinamente quiser uma cópia separada de um object? Você precisa criar um novo object e copiar as propriedades existentes nele. Duas formas modernas e concisas de fazer isso são Object.assign e a sintaxe spread.
Object.assign(target, ...sources) copia todas as propriedades próprias enumeráveis das fontes para o alvo e retorna o alvo:
A sintaxe spread {...obj} faz o mesmo de forma ainda mais curta (veja parâmetros rest e sintaxe 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 as técnicas produzem uma cópia real e independente — mas apenas no nível superior. Esse detalhe importa e nos leva à próxima seção.
O Problema com Objetos Aninhados
Uma cópia superficial duplica as propriedades do nível superior. Mas se o valor de uma propriedade for em si mesmo um object ou array, apenas a referência para esse object aninhado é copiada — não o próprio object aninhado. O clone e o original, portanto, compartilham o mesmo object aninhado.
Mesmo que clone seja um object de nível superior separado, clone.sizes e user.sizes apontam para o mesmo object aninhado. Mutá-lo por qualquer um dos caminhos afeta ambos. Esse é exatamente o tipo de bug de referência compartilhada acidental que surpreende quem assume que uma cópia com spread é "totalmente independente".
Spread ({...obj}) e Object.assign copiam apenas um nível de profundidade. Se seu object contém objetos ou arrays aninhados, a cópia compartilha esses valores aninhados com o original — mutar uma propriedade aninhada através de um alterará silenciosamente o outro. Para dados aninhados, use um deep clone.
Deep Cloning com structuredClone()
Para copiar um object e tudo que está aninhado dentro dele, use o structuredClone() nativo. Ele clona recursivamente objetos e arrays aninhados, de modo que o resultado é completamente independente do original.
structuredClone também lida com referências circulares (um object que, direta ou indiretamente, referencia a si mesmo) sem travar:
let user = {};
user.self = user; // user references itself
let clone = structuredClone(user);
console.log(clone === clone.self); // true — the cycle is preserved correctlyAlém de objetos e arrays simples, ele suporta muitos tipos nativos, incluindo Date, Map, Set, RegExp, ArrayBuffer e arrays tipados — copiando seus valores fielmente em vez de convertê-los em outra coisa.
Limitações do structuredClone
structuredClone é poderoso, mas não universal:
- Ele não consegue clonar funções — tentar fazê-lo lança um
DataCloneError. O mesmo se aplica a nós do DOM. - Ele não copia propriedades com chaves do tipo símbolo, getters/setters de propriedades, nem a cadeia de protótipos do object — esses elementos são simplesmente removidos do resultado.
// This throws DataCloneError because functions can't be cloned:
// structuredClone({ run: () => {} });Se você precisar copiar funções ou instâncias de classes com seus métodos, structuredClone não é a ferramenta certa — você escreveria um clone personalizado ou usaria uma biblioteca como o cloneDeep do lodash.
O Velho Truque do JSON
Antes de structuredClone estar amplamente disponível, um hack popular de deep clone era serializar um object para uma string JSON e fazer o parse de volta:
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 unaffectedFunciona para dados simples e seguros para JSON, mas tem armadilhas reais:
- Funções e
undefinedsão descartados — as chaves que os contêm simplesmente desaparecem. - Objetos
Datese tornam strings —JSON.stringifyconverte uma data em string ISO, e o parse não a converte de volta. Map,Sete outros tipos especiais se perdem ou se tornam objetos vazios.- Referências circulares lançam um
TypeError.
Para deep cloning, prefira structuredClone() ao truque JSON.parse(JSON.stringify(...)). A abordagem JSON perde silenciosamente funções, undefined e tipos especiais, deforma datas e trava em referências circulares — structuredClone lida com todos esses casos corretamente.
Uma Nota sobre Coleta de Lixo
Assim que você começa a copiar referências, vale lembrar como JavaScript recupera memória. Um object permanece na memória apenas enquanto é alcançável — enquanto alguma variável, propriedade ou entrada de array ainda o referencia. Quando a última referência a um object desaparece, ele se torna inalcançável e fica elegível para coleta de lixo. Você nunca libera objetos manualmente; o motor faz isso automaticamente.
Na Prática: Copiando Estado Antes de Mutar
Isso não é meramente acadêmico. No código de UI moderno (React, Redux e similares), a regra padrão é "nunca mute o estado diretamente — produza uma nova cópia com a alteração aplicada". Copiar superficialmente com spread é a ferramenta do dia a dia para isso:
Observe que fazemos spread tanto no object do nível superior quanto no array cart aninhado. Como spread é superficial, você deve copiar explicitamente cada nível aninhado que pretende alterar — caso contrário, você cai de volta na armadilha de referência compartilhada descrita anteriormente. Quando os dados são profundamente aninhados e você precisa de uma cópia completa e segura, use structuredClone.
Resumo
- Primitivos são copiados por valor — as cópias são totalmente independentes.
- Objetos são armazenados e copiados por referência — copiar a variável copia o ponteiro, então ambas as variáveis compartilham um único object.
===compara objetos por referência: apenas o mesmo object é igual a si mesmo.constfixa a ligação, não o conteúdo — objetosconstainda podem ser mutados.Object.assign({}, obj)e{...obj}fazem cópias superficiais que compartilham objetos aninhados.structuredClone(obj)faz uma cópia profunda, lida com referências circulares e muitos tipos nativos, mas não consegue clonar funções ou nós do DOM e remove chaves de símbolo, getters e protótipos.- Prefira
structuredCloneao truque com perdasJSON.parse(JSON.stringify(...)).