Análise comparativa: forEach vs map
- Lucas Akira
- Sem categoria
- abr 30, 2022
Quando falamos sobre arrays em JavaScript uma dúvida muito comum para muitos iniciantes (e até em algumas pessoas um pouco mais avançadas) é a diferença (e quando usar um ou outro) entre os métodos forEach e map. Por isso hoje iremos analisar bem cada um deles, e chegar a uma resposta para essa pergunta.
Entendendo a origem da dúvida
Para começar a nossa resposta, vamos a duas definições muito comuns de serem utilizadas para se explicar esses dois métodos:
O forEach é um método onde se aplica uma função a todos os itens de um array, semelhante a um laço de repetição.
E
O map é um método onde se aplica uma função a todos os itens de um array, e depois retorna um novo array com os resultados.
Só com isso já é possível notar o porquê algumas pessoas se sentem confusas em relação ao uso desses dois. Ambos servem para percorrer um array e aplicar uma função aos resultados desse array, sendo a sua única diferença o fato de que o map retorna um array e o forEach não.
E por ser uma diferença muito sútil, que nossa pergunta tema se origina, afinal muitas pessoas chegam a esse conteúdo depois de só terem contato com programação imperativa, então a coisa mais próxima dos conceitos de map e forEach (já que esses dois métodos são a representação desses conceitos para objetos do tipo array no JS) que essa pessoa vai conhecer é o laço de repetição. Então a parte da explicação que geralmente fica mais marcada na mente dessa pessoa é o fato de podermos aplicar uma função para cada item do array (assim como ela faria com um laço de repetição), e a diferença entre eles fica como algo bem secundário.
Isso origina uma outra dúvida, pois muitas vezes a pessoa até entende que o map retorna algo e o forEach não, porém ela fica na dúvida sobre a necessidade (ou quando usar) do forEach existir, afinal o map é a mesma coisa só que ele retorna um valor.
Então para responder a essa dúvida, vamos nos aprofundar no forEach e no map.
Explorando o ForEach
Começando pelo forEach, a inspiração por trás tanto dele quanto para o map é a programação funcional, e uma característica muito importante dela é o fato dela ser declarativa, então só vamos realmente entender a diferença entre os dois se olharmos desse ponto de vista e não do nosso usual imperativo (ou seja vamos pensar não no COMO e sim no QUE da coisa).
O forEach representa a ideia de podermos iterar sobre uma lista ou coleção (tanto é que ele está presente em outras estruturas do JS com o Set por exemplo), ele é um nível de abstração em cima de um laço de repetição.
Um laço de repetição clássico somente representa a ideia de repetir uma ação X número de vezes. Ex:
for (let x = 1; x <= 10; x++) {
console.log(x)
}
Aqui a ação de mostrar algo na tela (que no caso vai ser o próprio número do contador) vai ser repetida 10 vezes.
Por isso que no nosso primeiro contato com arrays, já aprendemos que podemos usar ela para iterar (percorrer) nossos arrays, afinal os itens contidos dentro de um array só podem ser acessados pelo seu indíce e os laços de repetição podem gerar esses indíces para gente, assim a gente executa um mesmo código repetidas vezes, mas mudando o indíce e assim acessando um item diferente por repetição. Ex:
const numbers = [1, 2, 3, 4, 5]
for (let index = 0; index < numbers.length; index++) {
console.log(numbers[index])
}
Então perceba que isso causa uma certa carga mental no seu cérebro para interpretar tudo que está acontecendo aqui, pois estamos interessados somente em aplicar um código para cada item do array, porém aqui temos que nos preocupar em iniciar o index no valor correto, de dizer ao JavaScript onde é o último index, e na hora de ler o numbers[index], temos que nos lembrar que ele é o item atual. Logo, não iríamos ter que pensar muito menos se só tivessemos uma variável representando o item atual direto, e não tivessemos que nos preocupar com configurar o for para iterar sobre todo o array e todos esses outros detalhes?
Foi pensando nisso que o for in e o for of foram criados no JavaScritpt, assim podemos ter acesso a todas as chaves do array sem nos preocupar com errar na hora de digitar o length, ou fazer a comparação errada na condição de parada, graças ao for in; e não temos que nos preoucupar nem em passar o indice errado, podendo ter acesso ao item atual direto por causa do for of. Ex:
const numbers = [1, 2, 3, 4, 5]
for (const number of numbers) {
console.log(number)
}
Mas não seria melhor ainda se pudessemos abstrair ainda mais esse processo? E é exatamente isso que o forEach faz. Ele é como se fosse uma junção desses dois na forma de uma única função, além de ter um nome que não indica uma ordem, e sim o que ele é, afinal se traduzirmos do inglês for each significa para cada. Dessa forma podemos escrever um código dessa forma aqui:
const numbers = [1, 2, 3, 4, 5]
numbers.forEach((number, index) => console.log(number, index))
E conseguimos ler esse código quase como se fosse uma frase: para cada número e indíce, console registre número e indíce.
Poderíamos até imaginar que uma implementação possível do forEach seja essa daqui:
function forEach(consumer, array) {
for (const index in array) {
consumer(array[index], index, array);
}
}
E note que ela é uma função que não retorna nada, por isso geralmente usamos o forEach quando queremos causar algum efeito colateral ao iterar sobre um array (tanto é que nossos exemplos mostram algo na tela).
Explorando o map
O map por outro lado representa a ideia de mapear um valor para o outro. Na matemática uma função é a representação de uma ponte entre um conjunto de entradas (o qual nós chamamos de domínio) e um outro conjunto (que nós chamamos de imagem). Ex:
{ 1; 2; 3; 4; 5 } // Conjunto domínio
|
V
f(x) = x + 1 // É aplica a função
|
V
{ 2; 3; 4; 5; 6 } // E nós obtemos o conjunto imagem
O conjunto imagem é o conjunto que representa todos os resultados da aplicação de f no conjunto domínio. Dessa forma forma podemos dizer que se a abstração que o forEach nos dá é a de iterar sobre os itens de um array, o map é como se fosse uma especialização do forEach, onde abstraimos o conceito de transformar um Array A em um Array B.
Para ficar mais fácil de visualizarmos isso, vamos ver como poderíamos hipotéticamente implementar um map. Ex:
function map(fn, array) {
const mappedArray = [];
array.forEach((item, index, original) => {
mappedArray.push(fn(item, index, original));
});
return mappedArray;
}
Então nós utilizamos o map (de preferencia com funções puras) quando queremos transformar o nosso array, note que não modificamos o nosso array original, nós criamos sempre um novo, e por isso não se deve usar o map no lugar do forEach, pois ao ter que gerar sempre um novo array, o custo para usar o map é sempre o dobro do custo para usar o forEach.
Fora isso, utilizar o map deixa claro a intenção do programador de transformar um array em outro, então assim como você não usaria um método filter no lugar do forEach por causa do significado dele, você não deveria utilizar o map no lugar do forEach por essa diferença nos significados.
Conclusão
Então para fechar esse post vamos dar uma olhada em um resumo de quando usar um e quando usar o outro.
Nós vamos utilizar o forEach quando:
- Quisermos apenas iterar sobre o array;
- Quisermos causar efeitos colaterais ao iterar sobre o array;
Nós vamos utilizar o map quando:
- Quisermos transformar um array A em um array B;