Desenvolvimento

6 set, 2018

O poder dos colchetes no JavaScript

Publicidade

Programadores experientes e até iniciantes já estão bem acostumados com as notações dot (ponto) e bracket (colchetes) não só na linguagem JavaScript, mas em diversas outras linguagens de programação. Todos já passamos pelo processo e pela necessidade de armazenar diversos valores em um mesmo agregado, uma mesma lista, um Array ou então um Object com suas propriedades e valores.

Até aí já estamos “craques”. Acessamos os valores do Array pela notação de colchetes ([]) passando a posição numérica do valor que queremos acessar nessa lista, e acessamos os valores dos objetos pela notação de ponto (.) seguido do nome da propriedade (ou chave) que queremos acessar.

var myArray = ["A", "B", "C", "D"];

  /*
    myArray[0] --> "A";
    myArray[1] --> "B";
    myArray[2] --> "C";
    myArray[3] --> "D";  
  */
  
var myObject = {
  one: "A",
  two: "B",
  three: "C",
  four: "D"
};

  /*
    myObject.one --> "A";
    myObject.two --> "B";
    myObject.three --> "C";
    myObject.four --> "D";  
  */

O que alguns anda podem se perguntar, e com razão, é por que nesses dois tipos particulares de elementos não podemos usar apenas uma ou apenas outra notação de acesso. Digitar um ponto é mais rápido do que abrir e fechar os colchetes passando a propriedade que queremos acessar, e abrir e fechar os colchetes deixa a propriedade mais visível para quem for ler o código e quiser saber o que está sendo acessado sem ter que ficar olhando para um monte de acentuações entre as palavras.

Nesse caso a resposta pode se definir por uma palavra: String. Propriedades dos objetos, suas chaves, são armazenadas como strings, afinal, são nomes, termos identificadores. Tentar acessar a propriedade nomeada como 0 em um objeto simplesmente com o nome dele seguido de ponto e o número, provocará um erro, já que o JavaScript espera avaliar uma string; ele pretende “ler” a propriedade para encontrar e realizar o que precisa.

var myObject = {
  0: "A",
  1: "B",
  2: "C",
  3: "D"
};

myObject.0; // --> Uncaught SyntaxError: Unexpected number

var myArray = ["A", "B", "C", "D"];

myArray.A // --> undefined
myArray. // lenght, concat, constructor, copyWithin, entries, every, ...

Tentar acessar os valores armazenados em um Array pela notação de ponto também acarretará em um erro, já que pelo ponto após a variável o JavaScript buscará as propriedades presentes no tipo do nosso objeto Array (length, find, every) e não os elementos que armazenamos nele, já que o Array é um tipo de objeto enumerável, ou seja, todos os seus valores, de quaisquer tipos que sejam, são identificados de forma ordenada em um lista, podem ser iterados e são encontrados de forma posicional e só.

Parte da graça começa quando descobrimos que podemos acessar as propriedades do nosso objeto por meio da notação de colchetes.

var myObject = {
  0: "A",
  1: "B",
  2: "C",
  2: "D",
  AbCDÇão: "BlaBlaBla"
};

myObject[0] // --> "A"
myObject["1"] // --> "B"
myObject["AbCDÇão"] // --> "BlaBlaBla"

Assim conseguimos pegar o valor da propriedade nomeada como 0 no nosso objeto sem o menor problema, bem como conseguimos pegar qualquer outra propriedade numérica, string com acentuações, string sem acentuações e de toda forma que nosso JavaScript pode aceitar, seguindo ou não as boas práticas e padrões de nomenclatura.

Let’s get it started

Se podemos acessar os valores das propriedades por meio da notação de colchetes com a string que será avaliada para encontrar o que queremos, também podemos usar para encontrar qualquer coisa armazenada na definição da variável. O que quero destacar desse conjunto enorme de coisas possíveis, são as funções.

Na notação de ponto, o JavaScript vai “ler por extenso” cada comando em cada linha. Ele fará processo direto de:

  • Ler o objeto, ler a propriedade, ler o objeto, ler a propriedade

Ao encontrar uma chamada de propriedade, ele vai localizar a referência ao objeto, avaliar a propriedade chamada, encontrá-la e então retorná-la ou executá-la. Sem enrolações.

Já na notação de colchetes, podemos muito bem trabalhar com retornos e cadeias de prioridade.

Em uma lógica muito comum, podemos, por exemplo, querer executar uma ou outra função dependendo de certa condição. Podemos querer adicionar uma classe a um elemento ou removê-la dependendo de outro resultado.

No mínimo quatro linhas funcionais seriam necessárias na notação de ponto para fazer esse processo.

  • Definição da condição
  • Implementação do resultado verdadeiro
  • Alternativa da condição
  • Implementação do resultado falso
if(element.classList.contains("some-class")){
  otherElement.classList.add("class1");
}else if(element.classList.contains("another-class")){
  otherElement.classList.remove("class1");
}

/* OR */

if(myStatusVariable){
  element.addEventListener("mouseover", myFunction);
}else{
  element.removeEventListener("mouseover", myFunction);
}

/* OR ELSE */

var element = (condition ? document.body : document.head);

Claro que também podemos usar uma operação ternária para esse caso. Identificaríamos várias vezes o elemento escrevendo por extenso cada sentença, mas manteríamos uma única linha.

Mas o ponto (ou colchetes) da questão está justamente em utilizar o ternário para executar tudo em uma única linha sem implementar função por função, e sim apenas identificar a chamada delas.

Podemos assim, definir dentro dos colchetes que acessará as propriedades do objeto, qual a função que deve ser retornada e executada segundo as condições que implementarmos.

Nesse processo, o JavaScript vai avaliar a linha do comando da seguinte maneira:

  • Encontrar o objeto
  • Avaliar o retorno definido
  • Encontrar a propriedade retornada
  • Executá-la (com parâmetros ou não)
element.classList[(element.classList.contains("some-class") ? "add" : "remove")]("other-class");

Aprofundando nos parâmetros, podemos também condicionar qual o parâmetro será passado para a execução da função retornada e ainda mais: se será passado mais de um parâmetro, podendo tratar dessa forma funções que diferem entre os valores que elas precisam, seja em tipo ou quantidade.

var status = null;
myObject[(condition ? (status = true, "replaceWordWithOther") : (status = false, "addWord"))]((status ? ("Hello", "Bye") : ("World")));

Considerando ou não o clean code e alguns padrões de codificação de lado por uns instantes, essa é uma funcionalidade muito poderosa da notação de colchetes.

  • Definir qual função será executada e com quais parâmetros sem precisar escrevê-los inteiramente

Com isso mente, se podemos acessar as funções e demais propriedades de um objeto tratando-o como um Array, como uma lista, o que temos implicitamente então é uma estrutura de decisão.

Se implicitamente o JavaScript realiza esse processo de iterar as propriedades definidas até encontrar a que foi chamada, por que também não fazer isso explicitamente?

Podemos substituir algumas estruturas de switch case, por exemplo, por um objeto que irá usar cada identificação de propriedade como uma condição para retornarmos ou executarmos o que precisamos.

var switchFunction = {
  0: "It`s true",
  1: function() {alert("Works!");}  
};

var condition = 0;
switchFunction[condition] // --> "It`s true"

condition = 1;
switchFunction[condition]() // --> alert("Works!")

Não há nenhuma questão de performance ou característica de sintaxe da linguagem envolvendo essas abordagens, mas são todas muito bem utilizáveis em projetos de diversos fins mostrando cada vez mais os bons recursos e técnicas da linguagem JavaScript e, mais especificamente, dos seus colchetes.