Basicamente, a imutabilidade é uma estrutura ou algo que não pode ser alterado. Na programação, esse conceito é aplicado a objetos e variáveis, principalmente em programação orientada a objetos, durante a criação ou quando um objeto está sendo instanciado.
Pelo fato de não poder ser alterado, automaticamente pensamos em utilizar constantes (const). Porém, não é bem assim! Uma variável declarada com constante não significa que o valor é imutável, mas apenas que a variável não pode ser alterada. Para entendermos mais, vamos nos aprofundar sobre os tipos de variáveis que existem no JavaScript e como se comportam em relação a imutabilidade.
[adrotate banner=”4″]
Tipos em JavaScript
Em Javascript, temos os tipos primitivos como:
- Boolean;
- Number;
- BigInt;
- String;
- Null;
- Undefined;
- Symbol.
Tipos primitivos
Os tipos primitivos são dados que não são representados através de um objeto. Além disso, todos os primitivos são imutáveis. Mas, se não são representados por um objeto, como conseguimos acessar algumas propriedades como: “.length” e “.toString”?
Isso é possível porque os tipos primitivos, com exceção do null e undefined, possuem um objeto wrappers que permite acessar algumas propriedades.
Quando chamamos os métodos como “.length” e .”tostring” nos tipos primitivos como number e string, por baixo dos panos, o JavaScript cria um objeto temporário por volta da variável, instância, método específico e em seguida a instância é excluída. Com isso, conseguimos utilizar métodos em variáveis do tipo primitivo.
No JavaScript podemos comparar variáveis através do operador de igualdade (‘==’) ou com o operador de identidade ‘===’ . A diferença é que o operador de igualdade irá converter as variáveis para o mesmo tipo antes de comparar os valores. Já o operador de identidade irá verificar se as variáveis são do mesmo tipo
e valor sem realizar conversões.
Comparação
Quando tentamos comparar tipos primitivos, o JavaScript compara o tipo e o valor, como no exemplo a seguir:
Sabendo que cada variável ocupa uma posição única de memória, ao compararmos os valores das variáveis a e b, como no exemplo/imagem a seguir, percebemos que o JavaScript considera como verdadeiro (true) o resultado da comparação entre as duas variáveis.
Além dos tipos primitivos, temos os tipos Objeto e Array. Se utilizarmos os operadores de igualdade e identidade para objetos e arrays, obteremos falso (false) na saída em todas as comparações. Isso porque o JavaScript irá comparar a referência na memória que cada objeto ou array ocupa, e não o tipo e o valor.
Para ilustrar a relação de igualdade entre os tipos Objeto e Array, considere as variáveis x (do tipo objeto), y (do tipo objeto) e z (uma cópia de x), como mostra a imagem logo abaixo.
Apesar das variáveis x e y serem do mesmo tipo, ao utilizarmos o operador de identidade, temos “false” como resultado. Pois fazem referência a diferentes posições de memória:
- x aponta para o endereço 0x01;
- e y para o endereço 0x02.
Entretanto, ao compararmos z e x, teremos um resultado verdadeiro (true), porque z e x apontam para a mesma referência na memória.
Aplicando imutabilidade e evitando bugs
Durante o processo de desenvolvimento, é comum a necessidade de manipular variáveis do tipo objeto e array. Algo como copiar alguns desses tipos de variáveis aparenta ser uma tarefa simples. Porém, quando não é aplicado o conceito de imutabilidade (no qual uma variável não pode ser alterada diretamente), isso pode causar alguns bugs em nossa aplicação.
Podemos iniciar uso da imutabilidade utilizando const para declarar nossa variáveis. Então, vamos ver isso na prática!
Imaginemos que possuímos uma variável “pessoa”, que é um objeto e possui as seguintes propriedades:
Agora, queremos copiar esse objeto para uma segunda variável chamada pessoa2 e alterar a profissão para UI/UX e executar um console log para analisarmos nossa saídas.
Como podemos observar na imagem, o objeto realmente foi copiado. Porém, ao alterar a propriedade “profissao” da pessoa2, a profissão da pessoa também foi alterada.
Isso aconteceu porque, ao criar o objeto pessoa, alocamos um espaço na memória e, como objetos e array trabalham com referência de memória, ao copiar o objeto, estamos na verdade copiando o seu endereço de memória e não o seu conteúdo.
Como podemos resolver isso?
Para copiar objetos e arrays, podemos utilizar o spread operator, representado por três pontinhos (…). Essa funcionalidade foi adicionada ao JavaScript no EcmaScript 2015. Assim, o spread tira uma cópia rasa (Shallow copy) do objeto ou array, criando um novo objeto ou array e gerando uma nova referência na memória.
Vamos ver como fica:
Para copiarmos o objeto e alterar alguma de suas propriedades, podemos fazer isso de diversas maneiras. Duas delas são:
Agora, quando queremos copiar objetos ou array alinhados como objetos dentro de objetos, precisamos fazer uma cópia profunda (deep copy) desse objeto.
Imaginemos que possuímos um objeto pessoa que tem um array de idiomas e queremos copiar esse objeto. Assim, faríamos da seguinte forma:
Dessa forma, conseguimos copiar o objeto de forma correta e garantir que, ao alterar o segundo objeto, iremos manter a imutabilidade do primeiro objeto que foi copiado e evitaremos bugs em nossa aplicação.
Sempre que for preciso alterar um objeto ou array, precisamos criar um novo com os valores atualizados e substituir o anterior.
Veja também:
O que é Injeção e Inversão de dependências?
Autor: Anderson Pablo.
[adrotate banner=”5″]