O JavaScript, assim como outras linguagens de programação, utiliza o símbolo “=” como sinal de atribuição de valor a uma variável.
Como uma linguagem de programação dinamicamente tipada, o JavaScript irá definir o tipo da variável de acordo com o valor atribuído.
Logo, você também consegue passar uma atribuição de variável para outra variável, como no exemplo abaixo:
Mas como é o comportamento quando usamos um objeto e tentamos atualizar as informações do objeto?
Mas porque será que isso acontece?
Gerenciamento de memória
O gerenciamento de memória na computação acontece em cima de duas tarefas:
Alocação de memória: responsável por “alugar” um bloco de memória para armazenar informações (como o valor de uma variável)
Reciclagem de memória: responsável por liberar um espaço de memória para reutilização ou realocação. Normalmente acontece quando um programa deixa de referência-lo.
A atribuição de uma variável em outra no JavaScript, está relacionado a uma referência a uma memória alocada na aplicação. Acontece que, quando você cria uma sentença como:
A variável1 está referenciando o valor da variavel2 até que algum dos dois seja reatribuído a uma nova alocação de memória. Exemplo:
Nesse caso, a variavel2 se torna a única referência ao valor original e a variavel1 acaba alocando um novo espaço de memória
PS: quando utilizamos atribuições em tipos primitivos, como em um número, e alteramos o valor do número com ações como += ou ++. estas ações estão reatribuindo o valor da variável, assim realocando um novo espaço na memória, o que explica o motivo pelo qual não passamos por este tipo de problema com tipos primitivos.
Estratégias para duplicar um objeto sem referenciá-lo no JavaScript
Sabendo que não podemos usar o valor de atribuição “=” para transferir um objeto a partir de um estado específico, podemos fazer isso de uma forma diferente. Confira algumas opções comuns:
Spread
Esta operação irá transformar o objeto em um objeto genérico (instâncias se tornam objetos), e irá copiar todos seus atributos primitivos para uma nova alocação de memória.
O que resolve o nosso problema, pelo menos para objetos não instanciados e com atributos primitivos.
Object.assign()
Esta operação irá preservar a alocação de memória de um objeto e atribuir ou referenciar todos os atributos primitivos de um outro objeto nele, sem que atribuições já existentes sejam perdidas.
A sua maior vantagem é que ela copia todos atributos, mas não sobrescreve os já existentes.
Podemos também utilizá-la em classes.
Problema ainda existente
Embora consigamos resolver uma cópia de atributos primitivos de um objeto, precisaríamos fazer a mesma coisa para cada filho não primitivo desse objeto caso seja preciso, como acontece nos exemplos abaixo:
Podemos ver que, quando clonamos um objeto, seja com Object.assign() ou spread, os filhos não primitivos deste objeto continuam sendo atribuídos como referência de memória e não uma nova alocação.
Deep Copy
Mas ainda assim, existem formas de resolver este problema, como vemos nos exemplo:
Também é possível desenvolver um método recursivo que iria fazer a clonagem de filhos de um objeto, usando a estratégia spread ou assign, mas que ficaria bem complexo e cansativo, não é?
A partir de uma atualização recente do JavaScript, é possível fazer isso nativamente usando o structuredClone().
Structured Clone
O método structuredClone() irá realizar uma transferência (não clonagem) recursiva de todos atributos de um objeto e retornará uma nova alocação. Gerando assim um novo objeto, totalmente desacoplado do original.
Desta forma, o structuredClone() é a melhor opção para realizar clonagens de objetos não instanciado atualmente.
Lembrando que ela não reatribui uma instância de objeto, como uma classe, neste caso você não terá acesso aos seus métodos e construtor, sendo recomendado utilizar o Object.assign() mesmo.
Compatibilidade
O método structuredClone() está disponível nas versões mais atuais dos principais navegadores e disponível para TypeScript a partir da versão 17.0.29 do @types/node.