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: LocalhostAqui, 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: undefinedComo 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)); // falseSelagem 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: 39Agora, 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: nameApó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); // TomasAqui, 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 novamenteNo 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); // TomPara 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.