W3docs

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.

javascript— editable

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.

javascript— editable

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 variable

A 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:

javascript— editable

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.

javascript— editable

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".

Aviso

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.

javascript— editable

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 correctly

Alé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 unaffected

Funciona para dados simples e seguros para JSON, mas tem armadilhas reais:

  • Funções e undefined são descartados — as chaves que os contêm simplesmente desaparecem.
  • Objetos Date se tornam stringsJSON.stringify converte uma data em string ISO, e o parse não a converte de volta.
  • Map, Set e outros tipos especiais se perdem ou se tornam objetos vazios.
  • Referências circulares lançam um TypeError.
Informação

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:

javascript— editable

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.
  • const fixa a ligação, não o conteúdo — objetos const ainda 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 structuredClone ao truque com perdas JSON.parse(JSON.stringify(...)).

Teste Seus Conhecimentos

Prática
Após let a = {}; let b = a; b.x = 1; — qual é o valor de a.x?
Após let a = {}; let b = a; b.x = 1; — qual é o valor de a.x?
Prática
Dado let c = { n: 1 }; let d = { n: 1 }; — o que c === d retorna?
Dado let c = { n: 1 }; let d = { n: 1 }; — o que c === d retorna?
Prática
Qual afirmação sobre a cópia de objetos em JavaScript está correta?
Qual afirmação sobre a cópia de objetos em JavaScript está correta?
Was this page helpful?