map, filter e reduce
Três métodos que resolvem, em uma linha cada, as três coisas que mais se faz com listas: transformar, peneirar e totalizar. O trio do JavaScript moderno.
Estudar esta lição interativa →Com um for...of, você consegue fazer qualquer coisa com uma lista. Mas, se prestar atenção no que os programas fazem com listas no dia a dia, três tarefas se repetem tanto que a linguagem criou métodos prontos para elas: transformar cada item em outra coisa (pegar os preços e aplicar 10% de desconto em todos), peneirar a lista mantendo só alguns itens (só os produtos em estoque) e juntar tudo em um único valor (o total do carrinho). São, na ordem, o map, o filter e o reduce.
Os três funcionam do mesmo jeito: você entrega a eles uma função dizendo o que fazer com cada item, e eles cuidam do loop por você. É aqui que as arrow functions do módulo passado brilham: aquele formato curtinho, (n) => n * 2, é perfeito para caber dentro dos parênteses do método. Antes do código, veja o desenho geral do que cada um faz com a lista:
Entra uma lista nos três, mas sai coisa diferente de cada um: do map sai uma lista nova com cada item transformado; do filter, uma lista menor só com os aprovados no teste; do reduce, um único valor que resume tudo.
Um por vez, começando pelo map. Ele visita cada item, aplica a sua função e monta uma lista nova, do mesmo tamanho, com os resultados. No exemplo abaixo tem uma novidade discreta: o .toUpperCase(), um método que os textos carregam consigo (assim como arrays têm os seus) e que devolve o texto em maiúsculas. É a primeira vez que ele aparece por aqui:
const numeros = [1, 2, 3, 4];
// .map() cria uma NOVA lista transformando cada item.
const dobrados = numeros.map((n) => n * 2);
console.log(dobrados); // [2, 4, 6, 8]
const nomes = ["ana", "bia", "caio"];
const maiusculas = nomes.map((nome) => nome.toUpperCase());
console.log(maiusculas);O filter é a peneira. A função que você passa é um teste: para cada item, ela deve responder true (mantém) ou false (descarta). Repare que são as comparações do Módulo 3 fazendo o trabalho, e que a lista resultante pode ser menor, mas os itens aprovados passam inalterados:
const idades = [15, 22, 8, 40, 17, 30];
// .filter() mantém só os itens que passam no teste (retornam true).
const adultos = idades.filter((idade) => idade >= 18);
console.log(adultos); // [22, 40, 30]
const pares = [1,2,3,4,5,6].filter((n) => n % 2 === 0);
console.log(pares); // [2, 4, 6]Por fim, o reduce, o mais poderoso e o que mais confunde à primeira vista. Ele existe para reduzir a lista inteira a um único valor. A boa notícia: você já conhece a mecânica dele. Lembra do padrão do acumulador, no módulo de loops (criar um soma = 0 antes do loop e ir somando a cada volta)? O reduce é exatamente isso, empacotado. A função dele recebe dois valores: o acumulado até agora e o item da vez; e aquele número solto no final (o 0) é o valor inicial do acumulador:
const precos = [10, 25, 5, 60];
// .reduce() "junta" a lista inteira em um único valor.
// (acumulador, itemAtual) => ... , valor inicial (0)
const total = precos.reduce((soma, preco) => soma + preco, 0);
console.log("Total: R$ " + total); // 100Com os três apresentados, fica a pergunta prática: qual usar? O atalho é olhar para o que você quer que saia do outro lado:
Quero cada item mudado
.map()Sai uma lista do MESMO tamanho, com cada item transformado pela sua função.
Quero só alguns itens
.filter()Sai uma lista menor (ou igual), só com os itens que passaram no seu teste.
Quero um valor só
.reduce()Sai um único valor: a soma, a média, o maior, o texto juntado...
Uma função que você entrega a outra função, para que ela a chame na hora certa, chama-se callback ("chamada de volta"). Já usamos isso sem o nome: a função de dentro do map, do filter e do reduce é um callback, e a função que vai ao addEventListener (que você verá no próximo módulo) também. Você escreve o que fazer; quem recebe decide quando e quantas vezes chamar.
Um detalhe elegante do trio: nenhum deles altera o array original. Eles sempre devolvem algo novo, e a lista de partida continua intacta. Isso evita sustos ("quem mudou meus dados?") e é considerado boa prática no JavaScript moderno: transformar gerando cópias, em vez de mexer no que já existia.
// Sua vez! Peneire a lista com filter.
const numeros = [5, 12, 8, 20, 3];
// Complete: mantenha só os números MAIORES que 10.
const grandes = numeros; // <- acrescente o .filter(...) aqui
console.log(grandes); // deve mostrar [12, 20]Complete o filter e clique em Rodar.