Cópias de objetos de forma eficiente com o StructuredClone
Entenda melhor como o JavaScript lida com cópias e serialização de objetos.
Por muito tempo, desenvolvedores utilizaram hacks e bibliotecas para criar deep clone de objetos no JavaScript, no entanto, hoje não é mais necessário pois a função nativa structuredClone()
veio para resolver esse problema. Mas antes de conhecer melhor essa alternativa, vamos entender melhor o problema.
Shallow copy
De modo geral, ao realizar uma cópia de um objeto, é feito uma shallow copy, ou seja, uma cópia superficial. Isso significa que, valores e propriedades aninhadas profundamente compartilham a mesma referência, apontando para os mesmos valores. Como resultado, quando uma propriedade é alterada na origem ou na cópia, o valor em ambos objetos será alterado, o que pode causar efeito colateral uma vez que possibilita uma alteração não intencional.
Uma forma comum de se criar shallow copy é utilizando a spread syntax:
Ao tentar alterar uma propriedade aninhada, ambos objetos são alterados:
O mesmo ocorre com propriedades do tipo Date
e Array
:
Porém, a cópia superficial não é uma exclusividade do spread operator. As funções padrões para cópias de objetos como Array.prototype.concat()
, Array.prototype.slice()
, Array.from()
, Object.assign()
, e Object.create()
também resultam em shallow copies.
Deep Copy
Por outro lado, é possível criar uma deep copy, ou cópias profundas, cujos valores e propriedades aninhadas profundamente não compartilham a mesma referência. Contudo, houve um tempo em que não havia uma forma simplificada de criar uma cópia profunda de objetos com JavaScript. Alguns desenvolvedores utilizavam biblioteca de terceiros como o cloneDeep()
do Lodash. A outra alternativa era um truque baseado em JSON:
No entanto essa última solução apresenta alguns problemas:
-
A função
JSON.stringify()
lança uma exceção com a mensagemTypeError: cyclic object value
caso seja utilizada uma uma estrutura de dados recursiva como uma circular linked list_. -
A função
JSON.stringify()
descarta valores comoundefined
,Function
,Symbol
uma vez que não são valores válidos para JSON. -
A função
JSON.stringify()
serializa apenas propriedades próprias enumeráveis. Isso significa queMap
,Set
e etc se tornarão{}
. -
Números como
Infinity
eNaN
, assim comonull
são consideradosnull
. -
Objetos do tipo
Date
são serializados para uma representação no formato de string, como1975-08-19T23:15:30.000Z
.
Sendo assim, a cópia de alguns objetos pode resultar em algo não desejado:
Structured clone
O Structured clone é um algoritmo que cria cópias de objetos complexos em JavaScript. Ele é extremamente útil quando se precisa transferir dados entre Workers por meio do postMessage()
, salvar objetos com IndexedDB ou copiar objetos para outras APIs. Ele clona de forma recursiva através do objeto de entrada, enquanto mantém um mapa das referências já visitadas, para evitar que percorra ciclos infinitamente.
Esse algoritmo é utilizado internamente quando se utiliza a função structuredClone()
disponível nos navegadores e no Node.js a partir da versão 17. Sendo assim, é possível nativamente clonar objetos complexos como:
Apesar de se tornar uma excelente alternativa para realizar deep copy de objetos, o structuredClone()
possui algumas limitações:
-
Caso esteja copiando uma instância de uma classe, será retornado um objeto simples contendo apenas seus valores uma vez que o algoritmo descarta o prototype chain do objeto.
-
Function
não são duplicadas pelo algoritmo lançando uma exceção do tipoDataCloneError
. -
Nós do DOM e instâncias de
Error
também lançam uma exceção do tipoDataCloneError
.
Conclusão
Caso seja necessário realizar uma cópia profunda de um objeto, o structureClone()
é uma ótima alternativa para a maioria dos casos. Contudo, o algoritmo structured clone ainda possui algumas limitações. Sendo assim, bibliotecas de terceiro, como o Lodash, podem ser uma alternativa ao provê uma implementação própria para deep copy que pode ou não atender ao seu caso.