Como Tornar Objetos Imutáveis em JavaScript
O idioma JavaScript nos permite alterar dinamicamente as propriedades dos objetos, adicionar novas propriedades e métodos ou remover os existentes. No entanto, tais alterações podem ser indesejáveis. Para isso, JavaScript oferece três mecanismos:
- Impedir a expansão de objetos
- Selagem de objetos
- Congelamento de objetos
Impedir a Expansão de Objetos
O método Object.preventExtensions()
permite impedir a expansão de um objeto, ou seja, novas propriedades e métodos não podem ser adicionados a esse objeto. Object.preventExtensions()
recebe o objeto alvo como parâmetro.
Primeiro, vamos ver um exemplo onde adicionamos uma nova propriedade com sucesso:
const tom = { name: "Tom" };
// adicionando uma nova propriedade - company
tom.company = "Localhost";
console.log(`Name: ${tom.name} Company: ${tom.company}`); // Name: Tom Company: Localhost
Aqui, uma nova propriedade company
é adicionada ao objeto tom
. Após a adição, podemos usar essa propriedade.
Agora, vamos impedir a expansão aplicando o método Object.preventExtensions()
:
const tom = { name: "Tom" };
Object.preventExtensions(tom); // impedindo a expansão do objeto tom
tom.company = "Localhost"; // tentando adicionar uma nova propriedade ao objeto tom
console.log(`Name: ${tom.name} Company: ${tom.company}`); // Name: Tom Company: undefined
Como resultado, mesmo que tentemos definir uma nova propriedade, ela não será adicionada. Ao tentar acessar tal propriedade, obtemos undefined
.
Às vezes, pode ser necessário determinar se um objeto é expansível. Para verificar a expansibilidade, você pode usar o método Object.isExtensible()
. Este método recebe o objeto em teste e, se o objeto suporta expansão, retorna true
; caso contrário, retorna false
.
const tom = { name: "Tom" };
console.log(Object.isExtensible(tom)); // true
Object.preventExtensions(tom); // impedindo a expansão do objeto tom
console.log(Object.isExtensible(tom)); // false
Selagem de Objetos
A selagem, ou "sealing", de objetos também permite impedir a expansão dos objetos. Além disso, proíbe a alteração das configurações das propriedades já existentes. O método Object.seal()
é usado para selar objetos.
Primeiro, vamos ver o que podemos fazer com um objeto antes de aplicar Object.seal()
:
const tom = { name: "Tom" };
// tornando a propriedade name não editável
Object.defineProperty(tom, "name", { writable: false });
tom.name = "Tomas";
// adicionando uma nova propriedade - age
tom.age = 39;
console.log(`Name: ${tom.name} Age: ${tom.age}`); // Name: Tom Age: 39
// permitindo a edição da propriedade name novamente
Object.defineProperty(tom, "name", { writable: true });
tom.name = "Tomas";
console.log(`Name: ${tom.name} Age: ${tom.age}`); // Name: Tomas Age: 39
Agora, aplicaremos o método Object.seal()
:
const tom = { name: "Tom" };
Object.seal(tom); // selando o objeto tom, impedindo a expansão e alteração de configurações
// tentando tornar a propriedade name não editável
Object.defineProperty(tom, "name", { writable: false });
tom.name = "Tomas";
// tentando adicionar uma nova propriedade - age
tom.age = 39;
console.log(`Name: ${tom.name} Age: ${tom.age}`); // Name: Tom Age: undefined
// tentando permitir a edição da propriedade name novamente
Object.defineProperty(tom, "name", { writable: true }); // Uncaught TypeError: Cannot redefine property: name
Após selar o objeto com Object.seal(tom)
, não podemos adicionar uma nova propriedade. Assim, no exemplo acima, a propriedade tom.age
será igual a undefined
, e também não podemos reconfigurar a propriedade. Aqui, ao tentar novamente chamar o método Object.defineProperty()
para a propriedade name, encontramos um erro "Uncaught TypeError: Cannot redefine property: name".
Para verificar se um objeto está selado, podemos usar o método Object.isSealed()
- se o objeto está selado, o método retorna true
. Vale ressaltar que, como um objeto selado é não expansível, o método Object.isExtensible()
retorna false
para ele.
Congelamento de Objetos com Object.freeze()
O congelamento, ou freezing, permite impedir a alteração dos valores das propriedades, tornando o objeto completamente constante. Por exemplo, definir um objeto com const
impede apenas a reatribuição da variável, mas não torna o conteúdo interno imutável:
const tom = { name: "Tom" };
tom.name = "Tomas";
console.log(tom.name); // Tomas
Aqui, vemos que o valor da propriedade do objeto mudou, embora o objeto seja definido como uma constante.
O operador const
afeta apenas o fato de que não podemos atribuir um novo valor à constante, como no seguinte caso:
const tom = { name: "Tom" };
tom = { name: "Sam" }; // Erro - não é possível atribuir um novo valor à constante novamente
No entanto, podemos alterar os valores das propriedades do objeto.
Para tornar um objeto verdadeiramente constante, é necessário aplicar o método especial Object.freeze()
. Este método recebe o objeto que deve ser tornado constante como parâmetro:
const tom = { name: "Tom" };
Object.freeze(tom);
tom.name = "Tomas"; // o valor da propriedade não pode ser alterado
console.log(tom.name); // Tom
Para verificar se os valores das propriedades de um objeto podem ser alterados, usa-se o método Object.isFrozen()
- se os valores das propriedades não podem ser alterados, ele retorna true
.
const tom = { name: "Tom" };
console.log(Object.isExtensible(tom)); // true
console.log(Object.isSealed(tom)); // false
console.log(Object.isFrozen(tom)); // false
Object.freeze(tom);
console.log(Object.isExtensible(tom)); // false
console.log(Object.isSealed(tom)); // true
console.log(Object.isFrozen(tom)); // true
É importante notar que um objeto "congelado" é o grau extremo de impedir alterações em um objeto. Assim, tal objeto é não expansível, e também não é possível alterar a configuração de suas propriedades.