A imutabilidade é um princípio central na programação funcional, e tem muito a oferecer também aos programas orientados a objetos. Um objeto mutável é um objeto cujo estado pode ser modificado após sua criação. Um objeto imutável é um objeto cujo estado não pode ser modificado após sua criação.
Qual é um exemplo de objeto imutável em JavaScript?
Em JavaScript, alguns tipos embutidos (números, strings) são imutáveis, mas objetos personalizados são geralmente mutáveis.
Alguns objetos JavaScript imutáveis embutidos são Math
, Date
.
Aqui estão algumas maneiras de adicionar/simular imutabilidade em objetos JavaScript simples.
Propriedades Constantes do Objeto
Combinando writable: false
e configurable: false
, você pode essencialmente criar uma constante (não pode ser alterada, redefinida ou excluída) como uma propriedade de objeto, como:
let myObject = {};
Object.defineProperty(myObject, 'number', {
value: 42,
writable: false,
configurable: false,
});
console.log(myObject.number); // 42
myObject.number = 43;
console.log(myObject.number); // 42
Impedir Extensões
Se você deseja impedir que um objeto tenha novas propriedades adicionadas a ele, mas de outra forma deixar o resto das propriedades do objeto intactas, chame Object.preventExtensions(...)
:
var myObject = {
a: 2,
};
Object.preventExtensions(myObject);
myObject.b = 3;
myObject.b; // undefined
Em modo não estrito, a criação de b
falha silenciosamente. Em modo estrito, ela lança um TypeError
.
Selar
Object.seal()
cria um objeto "selado", o que significa que ele pega um objeto existente e essencialmente chama Object.preventExtensions()
nele, mas também marca todas as suas propriedades existentes como configurable: false
.
Assim, você não pode adicionar mais propriedades, e também não pode reconfigurar ou excluir nenhuma propriedade existente (embora ainda possa modificar seus valores).
Congelar
Object.freeze()
cria um objeto congelado, o que significa que ele pega um objeto existente e essencialmente chama Object.seal()
nele, mas também marca todas as propriedades de "acesso a dados" como writable: false
, para que seus valores não possam ser alterados.
Essa abordagem é o nível mais alto de imutabilidade que você pode obter para um objeto em si, pois impede quaisquer alterações no objeto ou em qualquer uma de suas propriedades diretas (embora, como mencionado acima, o conteúdo de quaisquer outros objetos referenciados não seja afetado).
var immutable = Object.freeze({});
Congelar um objeto não permite que novas propriedades sejam adicionadas a um objeto e impede a remoção ou alteração das propriedades existentes. Object.freeze()
preserva a enumerabilidade, configurabilidade, gravabilidade e o protótipo do objeto. Ele retorna o objeto passado e não cria uma cópia congelada.
Quais são os prós e os contras da imutabilidade?
Prós
- Detecção de mudanças mais fácil - A igualdade de objetos pode ser determinada de forma performática e fácil através da igualdade referencial. Isso é útil para comparar diferenças de objetos em React e Redux.
- Programas com objetos imutáveis são menos complicados de pensar, pois você não precisa se preocupar com como um objeto pode evoluir ao longo do tempo.
- Cópias defensivas não são mais necessárias quando objetos imutáveis são retornados ou passados para funções, pois não há possibilidade de um objeto imutável ser modificado por ela.
- Compartilhamento fácil via referências - Uma cópia de um objeto é tão boa quanto outra, então você pode armazenar objetos em cache ou reutilizar o mesmo objeto várias vezes.
- Thread-safe - Objetos imutáveis podem ser usados com segurança entre threads em um ambiente multi-threaded, pois não há risco de serem modificados em outras threads em execução simultânea.
- Usando bibliotecas como ImmutableJS, objetos são modificados usando compartilhamento estrutural e menos memória é necessária para ter múltiplos objetos com estruturas semelhantes.
Contras
- Implementações ingênuas de estruturas de dados imutáveis e suas operações podem resultar em desempenho extremamente ruim porque novos objetos são criados a cada vez. É recomendado usar bibliotecas para estruturas de dados imutáveis e operações eficientes que aproveitam o compartilhamento estrutural.
- A alocação (e desalocação) de muitos objetos pequenos em vez de modificar os existentes pode causar um impacto no desempenho. A complexidade do alocador ou do coletor de lixo geralmente depende do número de objetos no heap.
- Estruturas de dados cíclicas, como grafos, são difíceis de construir. Se você tem dois objetos que não podem ser modificados após a inicialização, como você pode fazê-los apontar um para o outro?
Como você pode obter imutabilidade em seu próprio código?
A alternativa é usar declarações const
combinadas com as técnicas mencionadas acima para criação. Para "mutar" objetos, use o operador de espalhamento, Object.assign
, Array.concat()
, etc., para criar novos objetos em vez de mutar o objeto original.
Exemplos:
// Exemplo de Array
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // [1, 2, 3, 4]
// Exemplo de Objeto
const human = Object.freeze({ race: 'human' });
const john = { ...human, name: 'John' }; // {race: "human", name: "John"}
const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}