Já alguns anos, JavaScript chegou com métodos de manipulação de array mais voltados à programação funcional. Neste artigo, conheça a utilidade das funções map()
, filter()
e reduce()
através de exemplos de códigos, parâmetros e casos de uso.
Como programadores, construímos e manipulamos arrays de números, strings, booleanos e objetos quase todos os dias. Nós os usamos para processar números, coletar objetos, dar split em string, pesquisar, ordenar e muito mais. Mas qual é a melhor maneira de trabalhar com arrays? Tradicionalmente, tem sido usar loops para fazer a iteração nos elementos usando índice.
Em 2011, chegou em JavaScript map()
, filter()
e reduce()
como alternativas poderosas tanto para se trabalhar com valores cumulativos, quanto para criar subconjuntos com base em condições. Estes métodos são úteis para reduzir a complexidade, trabalhar sem “efeitos colaterais” e, muitas vezes, tornar o código mais legível.
Desvantagens do loop
Digamos que se está trabalhando em uma correção de bug e, vagueando por 1000 linhas de código JavaScript, eis que o seguinte loop se apresenta:
1 2 3 4 5 |
for ( var i = 0; i < array.length; i++ ) { if ( array.indexOf( array[ i ] ) === i ) { models.push(array[ i ] ); } } |
Foi possível ver que o loop serve para criar um novo array com elementos não duplicados no original. Mas, para descobrir isso, foi necessária uma análise em 5 etapas:
Código | Significado |
---|---|
var i = 0 |
Começa à esquerda do array |
i < array.length |
Termina à direita |
i++ |
Incremento de 1 em 1 |
array.indexOf( array[ i ] ) === i |
Se o valor é de primeira instância no array, vai coincidir com o índice (isso significa que se está verificando uma duplicata) |
models.push(...) |
models deve ser uma lista, mas quais dados ali constam? Quais são seus tipos de dados? É preciso procurar um arquivo “models”. Enxaguar. Repetir. |
É necessário verificar 5 pedaços de informações para determinar o que está acontecendo. E este é um único loop!
Uma abordagem funcional
Este mesmo objetivo poderia ser escrito usando o método filter()
de JavaScript da seguinte maneira:
1 2 3 |
var uniqueProducts = array.filter( function( elem, i, array ) { return array.indexOf( elem ) === i; } ); |
Simples e elegante.
Visto que filter()
comunica comportamento de código, então é possível saber exatamente o objetivo da função. Em comparação com a abordagem de looping acima:
- Checar #1, #2 e #3 é desnecessário, já que
filter()
faz isso automaticamente. - #4 é o mesmo, mas sem o adicional de checagem do bloco
if
. - Um grande obstáculo foi #5 — lembra-se do número de questões que ele suscitava?
map()
,filter()
ereduce()
resolvem esse tipo de problema ao não dependerem de código fora dos callbacks.
No final das contas, map()
, filter()
e reduce()
fazem do código menos complexo, sem efeitos colaterais e, freqüentemente, mais legível.
map()
Use map()
quando: é preciso traduzir/mapear todos os elementos em um array para outro conjunto de valores.
Exemplo: converter temperatura de Fahrenheit para Celsius.
1 2 3 4 5 6 7 8 9 10 |
var fahrenheit = [ 0, 32, 45, 50, 75, 80, 99, 120 ]; var celcius = fahrenheit.map( function( elem ) { return Math.round( ( elem - 32 ) * 5 / 9 ); } ); // ES6 // fahrenheit.map( elem => Math.round( ( elem - 32 ) * 5 / 9 ) ); celcius // [ -18, 0, 7, 10, 24, 27, 37, 49 ] |
O que map()
faz: percorre o array da esquerda para a direita invocando uma função de retorno em cada elemento com parâmetros. Para cada chamada de retorno, o valor devolvido se torna o elemento do novo array. Depois que todos os elementos foram percorridos, map()
retorna o novo array com todos os elementos “traduzidos”.
Parâmetros:
1 2 3 |
array.map( function( elem, index, array ) { ... }, thisArg ); |
Parâmetro | Significado |
---|---|
elem |
Valor do elemento |
index |
Índice em cada iteração, da esquerda para a direita |
array |
Array original invocando o método |
thisArg |
(opcional) Objeto que será referenciado como this no callback |
filter()
Use filter()
quando: é preciso remover elementos indesejados com base em alguma(s) condição(ões).
Exemplo: remover elementos duplicados de um array.
1 2 3 4 5 6 |
var uniqueArray = array.filter( function( elem, index, array ) { return array.indexOf( elem ) === index; } ); // ES6 // array.filter( ( elem, index, arr ) => arr.indexOf( elem ) === index ); |
O que filter()
faz: como map()
, filter()
percorre o array da esquerda para a direita invocando uma função de retorno em cada elemento. O valor retornado deve ser um booleano que indica se o elemento será mantido ou descartado. Depois de todos os elementos terem sido analisados, filter()
retorna um novo array com todos os elementos que retornaram como verdadeiro.
Parâmetros (os mesmo que map()
):
1 2 3 |
array.filter( function( elem, index, array ) { ... }, thisArg ); |
Parâmetro | Significado |
---|---|
elem |
Valor do elemento |
index |
Índice em cada iteração, da esquerda para a direita |
array |
Array original invocando o método |
thisArg |
(opcional) Objeto que será referenciado como this no callback |
reduce()
Use reduce()
quando: é preciso encontrar um valor cumulativo ou concatenado com base em elementos de todo o array.
Exemplo: soma de lançamentos de foguetes orbitais no período de 1 ano.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var rockets = [ { country:'Russia', launches:32 }, { country:'US', launches:23 }, { country:'China', launches:16 }, { country:'Europe(ESA)', launches:7 }, { country:'India', launches:4 }, { country:'Japan', launches:3 } ]; var sum = rockets.reduce( function( prevVal, elem ) { return prevVal + elem.launches; }, 0 ); // ES6 // rockets.reduce( ( prevVal, elem ) => prevVal + elem.launches, 0 ); sum // 85 |
O que reduce()
faz: como map()
, reduce()
percorre o array da esquerda para a direita invocando uma função de retorno em cada elemento. O valor retornado é o valor acumulado passado de callback para callback. Depois de todos os elementos terem sido avaliados, reduce()
retorna o valor acumulado/concatenado.
Parâmetros:
1 2 3 |
array.reduce( function( prevVal, elem, index, array ) { ... }, initialValue ); |
Parâmetro | Significado |
---|---|
prevVal |
Valor cumulado retornado em cada iteração |
elem |
Valor do elemento |
index |
Índice em cada iteração, da esquerda para a direita |
array |
Array original invocando o método |
initialValue |
(opcional) Objeto usado como primeiro argumento na primeira iteração (mais à esquerda) |
Conclusão sobre map()
, filter()
e reduce()
em JavaScript
Como informações extras sobre map()
, filter()
e reduce()
, dá-se que:
- Cada um deles é um método no objeto de protótipo (prototype) Array
- Alterar um elemento no parâmetro
array
em qualquer callback persistirá através de todos os callbacks restantes, mas não apresentará quaisquer efeitos no array retornado - Funções de callback são invocadas nos índices com qualquer valor, até
undefined
, mas não valores excluídos ou que nunca tiveram um valor atribuído
Importante ressaltar, também, que map()
, filter()
e reduce()
não vieram para substituir loops que, definitivamente, ainda têm seu lugar em diversas situações — como, por exemplo, ao se trabalhar com grandes arrays (mais de 1k elementos) ou quando é preciso parar a iteração se determinada(s) condição(ões) for(em) atendida(s) (break
).
Para continuar a conhecer as principais funções de array JavaScript, leia também nosso artigo every(), some(), find() e includes() em JavaScript.