Imutabilidade no ES6

Publicação: | Tags: JavaScript, Node.js

Imutabilidade no ES6

Caso você já esteja por dentro sobre o ES6/7/8/9/100000, você já deve estar bem ciente de como trabalhar com imutabilidade usando a keyword const para declarar varíaveis imútaveis. De fato não há nenhum segredo nisso, basicamente você declara qualquer tipo de dados usando const e nunca mais seu estado inicial será modificado, correto? Veja esse exemplo:

const score = 10;
console.log(score); // 10

score = 9; // TypeError: Assignment to constant variable

Até aqui tudo ok, foi criado uma constante score com valor 10, e ao tentar modificar seu valor para 9, ocorreu o erro: TypeError: Assignment to constant variable, que basicamente é um erro referente a não permissão de mudança de valores em uma variável que já possui um valor.

Outro detalhe da const, você não pode criá-la sem definir um valor, pois vai gerar o seguinte erro:

const score; // SyntaxError: Missing initializer in const declaration

É regra básica, não se pode criar constante sem definir seu estado inicial, pois undefined não é uma definição de um bom estado, não é?

Porém voltando a pergunta inicial, usar const torna qualquer tipo de dado do JavaScript imutável? NÃO!
Somente dados primitivos: String, Number, Date, Boolean, serão imútaveis, se você usar objeto ou array, facilmente você poderá modificar os atributos internos deles, o que teoricamente deveria ser imútavel também.

const scores = [];
scores.push(1);
scores.push(3);
scores.push(4);
console.log(scores); /// [1, 3, 4]

O mesmo cenário ocorre para objetos:

const player = {};
player.name = 'John Connor';
player.age = 25;
console.log(player); // { name: 'John Connor', age: 25 }

Nestes dois cenários, seus atributos internos foram modificados e nenhum erro ocorreu, afinal o const somente torna imútavel as atribuições das variáveis, ou seja, tudo aquilo que for atribuído usando = na inicialização de uma variável, qualquer atribuição em propriedades de um objeto serão ignorados.

Como tornar objetos e arrays imútaveis??

Desde o ES5, já existia uma função chamada Object.freeze() que congela o estado interno de um objeto ou array, ou seja, seus atributos jamais serão modificados, congelando seu estado, e não permitindo a inclusão, alteração e exclusão de seus atributos internos, entenda nesse exemplo:

const player = { name: 'John Connor' };
Object.freeze(player);
// Silenciosamente não vai acontecer nenhum erro
player.age = 25; 
// Porém não será incluído o atributo age no objeto que foi congelado!
console.log(player); // Object { name: 'John Connor' }

No caso do array, qualquer modificação vai gerar um erro, veja:

const scores = [1, 2, 3];
Object.freeze(scores);
scores.push(10); // TypeError: Can't add property 10, object is not extensible

Caso seja necessário visualizar um erro na modificação de um objeto, basta usar 'use strict' no topo do código:

'use strict';
const player = { name: 'John Connor' };
Object.freeze(player);
player.age = 25; // TypeError: Can't add property age, object is not extensible

Aplicando deep frezzing recursivo

Mesmo usando Object.freeze() para congelar o estado do objeto ou array, sub-atributos ou sub-objetos vão continuar mútaveis, para realmente criar um objeto/array profundamente imútavel, é necessário criar uma função recursiva para aplicar o conceito de deep freezing, veja abaixo como fazer isso:

function deepFreeze(obj) {
  const props = Object.getOwnPropertyNames(obj);
  props.forEach(function(name) {
    const prop = obj[name];
    if (typeof prop === 'object' && prop !== null) {
      deepFreeze(prop);
    }
  });
  return Object.freeze(obj);
}

Agora com essa função declarada, você não vai conseguir modificar subatributos dos subatributos dos subatributos, que todos eles serão imútaveis, veja:

const data = {
  cliente: {
    nome: 'John Connor'
  }
}

// Usando apenas Object.freeze()
Object.freeze(data);
data.cliente.nome = 'Sarah Connor'
console.log(data.cliente.nome); // Sarah Connor

// Usando função deepFreeze()
deepFreeze(data);
data.cliente.nome = 'Sarah Connor'
console.log(data.cliente.nome); // John Connor

Conclusão

Se você pretende trabalhar 100% com dados imútaveis e isso inclui tanto dados primitivos quanto objetos e arrays, basta usar const para as variáveis primitivas e const + Object.freeze() para objetos e ou arrays.

Recentemente lancei o ebook JavaScript Awesome Tips (Br version) com diversas dicas sobre JavaScript, vale a pena a leitura!

Fonte: MDN - Object.freeze()

Compartilhe este post