JavaScript

12 abr, 2022

Entendendo os laços “for” do JavaScript para interação com arrays

Publicidade

Uma das primeiras dúvidas que rapidamente vieram à minha cabeça quando comecei a estudar JavaScript foi: “Ok, o JavaScript tem muitas opções de loops for, mas qual é a melhor delas? Qual eu devo usar para lidar com arrays?”. A resposta, como a maioria das respostas em programação, é: depende. Depende de qual o seu objetivo principal ao utilizar o loop.

Ao longo deste artigo darei uma visão geral e minha opinião sobre cada um dos seguintes loops: forfor...offor...inforEach.

Primeiramente (para os mais interessados em performance) farei uma análise comparativa de tempo de processamento entre cada um dos quatro loops. Posteriormente falarei sobre os pontos positivos e negativos (na minha visão) ao usar cada um deles nos seguintes aspectos:

  • Legibilidade
  • Facilidade de acesso à informação
  • Complexidade
  • Intuitividade

> Nota: o intuito deste artigo não é ensinar como declarar nenhum desses loops, por isso não me estenderei muito em suas possíveis implementações.

Performance é importante (mas não é tudo)

Normalmente ao analisar diferentes opções de implementação de uma operação com os mesmos objetivos é comum que a performance ganhe uma atenção especial, afinal ela é muito importante nas nossas aplicações.

Sendo assim, para quem quer ir “direto ao ponto”, apresento um demonstrativo de um pequeno teste que efetuei com cada um dos loops analisados neste artigo utilizando o seguinte código simples:

A ferramenta utilizada para fazer a aferição dos tempos foi a interface performance do JavaScript.

Sequência de testes: array com 100.000 elementos

Tabela elaborada pelo autor

Sequência de testes: array com 1.000 elementos

Tabela elaborada pelo autor

Sequência de testes: array com 100 elementos

Tabela elaborada pelo autor

Sequência de testes: array com 10 elementos

Tabela elaborada pelo autor

Observando as tabelas acima, é possível concluir que:

  • Para arrays que possuem um número maior de elementos, a opção mais performática de loop é o forEach
  • Já para arrays que possuem um menor número de elementos, a opção mais performática é o clássico loop for.

Mas, como foi dito no início deste tópico, performance é importante, mas não é tudo. Os tópicos a seguir darão uma abordagem mais específica (que vai além de análise de performance) de pontos positivos e negativos de cada um dos loops testados.

O clássico ‘for’

A estrutura básica de um loop for simples é a seguinte:

for (initialValue; finalCondition; increment) {
  // codigo;
}
  • initialValue: geralmente uma variável numérica;
  • finalCondition: quando o loop deve parar de ser executado;
  • increment: como a variável deve ser incrementada.

Um exemplo prático de utilização seria:

Considere que temos um array de objetos correspondentes à informações de alguns carros e desejamos saber qual dos carros é o mais veloz:

Este código percorre o array cars e faz a comparação entre as diferentes velocidades máximas de cada um. Ao final da iteração ele mostra no terminal qual é o carro mais rápido, sua marca, seu modelo e a velocidade máxima alcançada.

Pontos positivos

  • Facilidade de acesso à informação: este loop permite que o índice do elemento em que estamos seja facilmente acessado, basta acessarmos o valor da variável index.
  • Intuitividade: apesar de complexo, é um loop intuitivo, uma vez que se compreende as premissas de seu funcionamento.

Pontos negativos

  • Facilidade de acesso à informação: se por um lado, conseguimos acessar a posição do elemento com facilidade, por outro lado, para acessarmos o elemento em si é necessário utilizar a sintaxe cars[index], o que torna o acesso ao elemento indireto e menos intuitivo em comparação ao forEachou for...of
  • Legibilidade: a estrutura em si não colabora muito para a legibilidade do código. Há a necessidade de olharmos para mais de uma variável para sabermos qual o elemento tratado. Além disso, não há muita elegância neste formato.
  • Complexidade: dentre os quatro loops analisados, na minha visão, este é o mais complexo, por possuir diversos elementos e passos em sua construção.

O poderoso ‘forEach’

forEach é um loop que se diferencia dos outros três por ser um método do objeto array. Sua estrutura básica de um loop forEach é a seguinte:

arr.forEach(callbackFunction(currentElement [, currentElementIndex[, array]])[, thisArg]);
  • callbackFunction: uma função que será aplicada à cada elemento do array durante a iteração.
  • → currentElement: o elemento atual da iteração
  • → currentElementIndex: índice que indica a posição do elemento no array
  • → array: o próprio array sobre o qual a iteração está ocorrendo
  • thisArg: argumento usado para indicar o contexto da função

Utilizando nosso exemplo inicial, para determinar qual o carro mais veloz, o código com o forEach seria este:

Assim como no exemplo anterior, este código percorre o array cars e determina qual o carro mais veloz, porém com algumas diferenças: agora temos acesso direto a um elemento do array sem precisar referenciá-lo como cars[i], o forEach é um método, diferentemente dos outros três loops.

Pontos positivos

  • Facilidade de acesso à informação: o forEachpermite que tenhamos acesso direto ao elemento com o qual se está interagindo naquele determinado momento e também ao seu índice.
  • Legibilidade: para um estudante que está iniciando na linguagem talvez não seja algo tão legível, mas uma vez que já se conhece o conceito de arrow functions fica mais fácil entender o que está acontecendo.
  • Intuitividade: é um loop intuitivo, já que sua dinâmica é facilmente compreendida e as informações são explícitas.

Pontos negativos

  • Complexidade: como dito acima, para entender este loop geralmente é necessário um certo conhecimento prévio de algumas funcionalidades da linguagem, como callbacks e arrow functionsPor isso diria que não é um loop que eu indicaria como primeira opção para iniciantes que estão conhecendo a linguagem.

> Nota: não é recomendável utilizar o forEach com código assíncrono, pois mesmo com o uso do async-await ele não espera cada promise ser resolvida antes de finalizar suas iterações, o que pode causar bugs no código. Se precisar de chamadas assíncronas dentro do loop utilize alguma das outras opções, ou outra opção que satisfaça suas necessidades.

O elegante ‘for…of’

De todos os quatro loops que estamos vendo, o for...of sem dúvida é o que mais me cativa. Principalmente porque minha linguagem favorita é o Python (quem conhece de Python vai entender o motivo já já). Sua estrutura básica é a seguinte:

for (const currentElement of iterable){
  // code
}
  • currentElement: o elemento atual da iteração
  • iterable: pode ser qualquer objeto iterativo (incluindo Arrays)

Para descobrir o carro mais veloz neste caso, faríamos algo como isto:

Como eu disse no título deste tópico, na minha opinião, o for...of é o loop mais elegante dentre os quatro analisados.

Pontos positivos

  • Legibilidade: sua leitura é extremamente fácil, não possui muitos elementos que poderiam confundir o leitor.
  • Facilidade de acesso à informação: este loop permite o acesso direto ao elemento sobre o qual se está iterando.
  • Complexidade: quase não há, é um loop extremamente simples de se entender
  • Intuitividade: mesmo alguém que não conhece JavaScript, mas que possui conhecimento de alguma outra linguagem conseguiria inferir corretamente o que este loop se propõe a fazer apenas lendo-o.

Pontos negativos

  • Facilidade de acesso à informação: nos casos em que é necessário ter acesso ao índice do elemento sobre o qual se está iterando, o for...of não proporciona esta informação diretamente. Mas como tudo tem solução nessa vida, aqui está um link para o stack overflow que soluciona seu problema, se você precisar.

O contra-intuitivo ‘for…in’

De todos os quatro, este loop é o que eu menos tenho apreço, apesar de ele ter seu valor, como todos os outros. Sua sintaxe básica é a seguinte:

for (enumProperty in object) {
  // code
}
  • enumProperty: a propriedade enumerável atual do objeto sobre o qual se interage
  • object: o objeto sobre o qual se está iterando

Se utilizarmos este loop, com o nosso exemplo, ele ficaria assim:

É preciso tomar muito cuidado com este loop, pois ele interage com as propriedade enumeráveis do objeto e não com seus elementos propriamente ditos. Isto quer dizer, no caso de um array, que ele vai interagir com os índices posicionais de cada elemento. Além disso, existem outras particularidades sobre este loop que não irei abordar aqui, mas se quiser conhecer mais sobre o for...in, acesse sua documentação.

> Uma nota da própria documentação da Mozilla diz que o “ for...in não deve ser usado para iteração em uma Array onde a ordem é importante, visto que ele interage em uma ordem arbitrária.”

Pontos positivos

  • Legibilidade: não existem muitos elementos confundindo a leitura do código.
  • Facilidade de acesso à informação: ele permite que seja acessado o índice do elemento atual no array.
  • Complexidade: é um loop extremamente simples de se compreender, uma vez que se conhece seu mecanismo de funcionamento.

Pontos negativos

  • Facilidade de acesso à informação: não permite acesso direto a um elemento do array.
  • Intuitividade: é um loop completamente contra-intuitivo e que pode gerar confusões, principalmente porque, lembre-se, ele interage com as propriedades enumeráveis do array, e não necessariamente com seus índices posicionais.

Para fechar

De uma forma geral, existem duas situações mais comuns: a primeira é quando você precisa de performance, pois está lidando com uma grande quantidade de elementos no array; a segunda é quando você não tem tanta preocupação com a performance pois seu volume de dados não é tão grande, mas precisa escrever um código extremamente inteligível a quaisquer olhos pois, na maioria das vezes, escrevemos códigos para outras pessoas lerem e colaborarem conosco e não apenas para a máquina executar. Na primeira situação, eu recomendo o uso do forEach(desde que não haja chamadas assíncronas na função de callback), e na segunda o for...of.

Obviamente, esta é minha opinião, e foi baseada nos conhecimentos que possuo sobre o assunto tratado neste artigo, nada escrito em pedra, portanto, a dica real é: utilize a opção que melhor atende a sua necessidade e te deixa mais confortável com o código escrito.