Array - filter e map - JavaScript - TypeScript - Node.js

Como filtrar ou mapear um novo array utilizando Array.filter e Array.map em JavaScript - Node.js - TypeScript. Veja como estas funções funcionam e reduza suas linhas de código.

Ilustração de um array e as funções map, filter e reduce

Estas funções, filter e map com certeza são as funções que mais utilizo quando trabalho com arrays em JavaScript e o motivo é bem simples, chegar no mesmo resultado com menos linhas de códigos.

Sem mais delongas, vamos considerar a seguinte massa de dados para os exemplos abordados neste artigo:

const dados = [
    { id: 1, nome: 'Nome 1', valor: 10 },
    { id: 2, nome: 'Nome 2', valor: 20 },
    { id: 3, nome: 'Nome 3', valor: 30 },
    { id: 4, nome: 'Nome 4', valor: 40 },
    { id: 5, nome: 'Nome 5', valor: 50 },
];

JavaScript - Array.prototype.map()

Quando utilizar: tenho um array e gostaria de transformá-lo em um outro array.

Exemplo 1 - array de objetos para array de string

Vamos supor que seja necessário transformar o array dados (Array<any>) em um array de string (Array<string>) onde cada posição seja igual a propriedade nomede cada item do array dados. O resultado final seria:

[ 'Nome 1', 'Nome 2', 'Nome 3', 'Nome 4', 'Nome 5' ]

Para chegar no resultado acima podemos utilizar um loop for:

const novoArray = [];
for (let i = 0; i < dados.length; i++) {
    novoArray.push(dados[i].nome);
}

Como alternativa podemos utilizar a função map() para chegar no mesmo resultado porém com uma redução significativa de 4 linhas de código para apenas 1:

const novoArray = dados.map(item => item.nome);
Edit fiddle - JSFiddle - Code Playground
Test your JavaScript, CSS, HTML or CoffeeScript online with JSFiddle code editor.
Exemplo da implementação acima

Exemplo 2 - array de objeto A para array de objeto B

O array dados tem 3 informações para cada item. Vamos utilizar novamente um loop for para criar um novo array com apenas 2 informações:

  • id
  • nome + valor - nome concatenado com o valor
const novoArray = [];
for (let i = 0; i < dados.length; i++) {
    const item = dados[i];
    novoArray.push({
        id: item.id,
        nome: `${item.nome} - ${item.valor}`
    });
}

console.log(novoArray);

Obtendo exatamente o mesmo resultado utilizando o método .map():

const novoArray = dados.map(item => {
    return {
        id: item.id,
        nome: `${item.nome} - ${item.valor}`
    };
});

console.log(novoArray);
[{
  id: 1,
  nome: "Nome 1 - 10"
}, {
  id: 2,
  nome: "Nome 2 - 20"
}, {
  id: 3,
  nome: "Nome 3 - 30"
}, {
  id: 4,
  nome: "Nome 4 - 40"
}, {
  id: 5,
  nome: "Nome 5 - 50"
}]
O resultado é o mesmo utilizando o "for" ou o "map"
Edit fiddle - JSFiddle - Code Playground
Test your JavaScript, CSS, HTML or CoffeeScript online with JSFiddle code editor.
Implementação do exemplo acima - array - for vs map - JavaScript

Apesar de ser um exemplo bem simples, suponha que sua API faça uma consulta a uma outra API que retorne 10 campos. Sua API deve enviar para o frontend apenas 3 campos porque os outros 7 não serão utilizados. Veja que neste caso o .map pode ajudar.

Ideia por trás do Array.prototype.map()

Para entender como o map() funciona, vamos construir uma função chamada meuMap que retorne o mesmo resultado do map. Primeiro vamos criar uma nova função que seja aplicada a todo Array:

Array.prototype.meuMap = function() {
    console.log('olá! dados do array: ', JSON.stringify(this));
};

// Agora todo array poderá utilizar a função meuMap
[1, 2, 'marcelo'].meuMap();

// Resultado no console:
// olá! dados do array:  – "[1,2,\"marcelo\"]"

Veja que o this dentro da função meuMap nos dá acesso a todos os itens do array. É possível observar este comportamento no resultado do console, também acima.

Vamos evoluir este código retirando este console.log e retornando um novo array com o mesmo conteúdo do array de origem:

Array.prototype.meuMap = function() {
    const novoArray = [];
    
    // loop em cima do array original (this)
    for (let i = 0; i < this.length; i++) {
        novoArray.push(this[i]);
    }
    
    return novoArray;
};

// Agora todo array poderá utilizar a função meuMap
const teste = dados.meuMap();
console.log(teste);
[
    { id: 1, nome: 'Nome 1', valor: 10 },
    { id: 2, nome: 'Nome 2', valor: 20 },
    { id: 3, nome: 'Nome 3', valor: 30 },
    { id: 4, nome: 'Nome 4', valor: 40 },
    { id: 5, nome: 'Nome 5', valor: 50 },
];
Resultado do console acima - veja que o resultado tem o mesmo conteúdo do array dados

Apesar se estarmos retornando um novo array, o resultado final tem o mesmo formato do array de origem. Então como customizar este retorno para modificar o array de saída? ou em outras palavras, como transformar o array de origem em um outro array?

Para fazer isto podemos parametrizar uma função que receba um objeto e retorne outro objeto, algo do tipo:

// Criamos um novo parâmetro (funcaoTransformacao):
Array.prototype.meuMap = function(funcaoTransformacao) {
    const novoArray = [];
    
    for (let i = 0; i < this.length; i++) {
        // Para cada item do array, chamaremos a 
        // funcaoTransformacao:
        const thisTransformado = funcaoTransformacao(this[i]);
        novoArray.push(thisTransformado);
    }
    
    return novoArray;
};

function retornaNome(entrada) {
    return entrada.nome;
}

// Agora todo array poderá utilizar a função meuMap
const teste = dados.meuMap(retornaNome);
console.log(teste);

// Resultado no console:
// ["Nome 1", "Nome 2", "Nome 3", "Nome 4", "Nome 5"]

Como alternativa, podemos remover a declaração explicíta function retornaNomee utilizar uma arrow function:

const teste = dados.meuMap((item) => item.nome);
console.log(teste);
Exemplo da utilização da função meuMap para transformar o array dados em um array de string

Veja que chegamos em como a função .map() deve funcionar, então vamos avançar mais um pouco nesta função falando do índice.

Índice - index

O .map() também dá acesso ao índice, ou seja, é como se ele passasse para a "função de transformação" o i do for. Pegando carona no exemplo acima, a ideia é a seguinte:

Array.prototype.meuMap = function(funcaoTransformacao) {
    const novoArray = [];
    
    for (let i = 0; i < this.length; i++) {
        // ****************************************************
    	// Veja que agora passamos o "i" além do item do array:
        // ****************************************************
        const thisTransformado = funcaoTransformacao(this[i], i);

        novoArray.push(thisTransformado);
    }
    
    return novoArray;
};

// ************************************
// Agora a função recebe também o index
// ************************************
function retornaNome(entrada, index) {
    return entrada.nome + ' - i: ' + index;
}

// Agora todo array poderá utilizar a função meuMap
const teste = dados.meuMap(retornaNome);
console.log(teste);

// Resultado no console:
// [
//     "Nome 1 - i: 0",
//     "Nome 2 - i: 1",
//     "Nome 3 - i: 2",
//     "Nome 4 - i: 3",
//     "Nome 5 - i: 4"
// ]

Então podemos concluir que o .map() faz um loop em cima do array e para cada item ele chama uma função que é passada como parâmetro no (Array.map(funcao, índice <opcional>)).

Exemplo 3 - construindo um array para mock

Aqui vai um script bem legal que sempre utilizo quando preciso de uma massa (array) rápida para testar alguma funcionalidade:

console.log(Array(10).fill('').map((a, i) => `Item ${++i}`));

// resultado:
// [
//     "Item 1",
//     "Item 2",
//     "Item 3",
//     "Item 4",
//     "Item 5",
//     "Item 6",
//     "Item 7",
//     "Item 8",
//     "Item 9",
//     "Item 10"
// ] 

// ou também
const novoArray = Array(10)
    .fill('')
    .map((a, i) => {
        return {
            id: ++i,
            nome: `Item ${++i}`
        }
    });
    
console.log(novoArray);

// Resultado
// [
//     {id: 1, nome: "Item 2"},
//     {id: 2, nome: "Item 3"},
//     {id: 3, nome: "Item 4"},
//     {id: 4, nome: "Item 5"},
//     {id: 5, nome: "Item 6"},
//     {id: 6, nome: "Item 7"},
//     {id: 7, nome: "Item 8"},
//     {id: 8, nome: "Item 9"},
//     {id: 9, nome: "Item 10"},
//     {id: 10, nome: "Item 11"}
// ]

JavaScript Array.prototype.filter()

Quando utilizar: tenho um array e preciso selecionar apenas alguns itens deste array gerando um novo array.

Exemplo 1 - filtrando registros com id par

Vamos filtrar o array dados pegando apenas os registros que tenham um id par. Primeiramente vamos utilizar um loop for:

const novoArray = [];
for (let i = 0; i < dados.length; i++) {
    if (dados[i].id % 2 === 0) {
        novoArray.push(dados[i].nome);
    }
}

Agora utilizando o .filter para obter o mesmo resultado:

const novoArray = dados.filter(item => item.id % 2 === 0);

Independente do for ou do filter o resultado será exatamente o mesmo, novamente a diferença está na quantidade de código:

[{
  id: 2,
  nome: "Nome 2",
  valor: 20
}, {
  id: 4,
  nome: "Nome 4",
  valor: 40
}]
Edit fiddle - JSFiddle - Code Playground
Test your JavaScript, CSS, HTML or CoffeeScript online with JSFiddle code editor.
Exemplo da implementação do exemplo acima

Ideia por trás do filter()

Vamos pegar uma "carona" no exemplo do map() para construir nossa própria função se baseando na ideia ideia do filter :

Array.prototype.meuFilter = function() {
    const novoArray = [];
    
    for (let i = 0; i < this.length; i++) {
        // ************************
        // TODO Como vamos filtrar?
        // ************************
        novoArray.push(this[i]);
    }
    
    return novoArray;
};

O trecho de código acima mostra que estamos criando uma função para os nossos arrays chamada meuFilter. Então todo array terá acesso a esta função. O problema é que nossa função ainda não filtra nada porque precisamos de uma condição que esteja parametrizada para atender aos diferentes cenários, ou seja, as vezes quero filtrar os itens cujo o id seja par, ou filtrar itens cujo o nome comece com a letra 'm' e assim por diante.

Então vamos adicionar um parametro que irá filtrar o array:

// *******************************************************
// Adicionamos um novo parâmetro que representa uma função
// que receberá alguma coisa e devolverá true ou false
// *******************************************************
Array.prototype.meuFilter = function(funcaoFiltro) {
    const novoArray = [];
    
    for (let i = 0; i < this.length; i++) {
        // ************************************************
        // Veja que o this[i] (item do array de origem)
        // será incorporado ao retorno apenas se a condição
        // do funcaoFiltro for satisfeita
        // ************************************************
        if (funcaoFiltro(this[i])) {
            novoArray.push(this[i]);
        }
    }
    
    return novoArray;
};

// ****************************************************************
// Testando nossa função meuFilter utilizando array function inline
// ****************************************************************
const novoArray = dados.meuFilter(item => item.id % 2 === 0);

// ou

function meuFiltro(item) {
    return item.id % 2 === 0;   
}
const novoArray = dados.meuFilter(meuFiltro);

Veja que o resultado utilizando o meuFilter é exatamente o mesmo que utilizando o filter.

[{
  id: 2,
  nome: "Nome 2",
  valor: 20
}, {
  id: 4,
  nome: "Nome 4",
  valor: 40
}]

O objetivo aqui foi mostrar como a função filter funciona, pois se você souber replicá-la, com certeza saberá utilizá-la, certo?

Edit fiddle - JSFiddle - Code Playground
Test your JavaScript, CSS, HTML or CoffeeScript online with JSFiddle code editor.
Exemplo comparando o array.filter com o array.meuFilter

Considerações

Abordamos apenas 2 funções que o objeto Array nos fornece mas existem diversas outras que podem ajudar em outros cenários: reduce, flat, sort, find, some, entre outras.

Apesar de não abordado, o método filter também possuí um segundo parâmetro em seu callback para retornar o índice, exatamente como no map. Também vale mencionar que ambos,  filter e map, recebem como parâmetro uma função (callback) que recebe 3 parâmetros:

// Callback é a função que passamos como parâmetro
// no filter ou map.
// Este callback recebe os 3 parâmetros conforme abaixo.
dados.filter((item, indice, arrayOriginal) => {
    // TODO
});

dados.map((item, indice, arrayOriginal) => {
    // TODO
});

Caso você não utilize o indice e o arrayOrignal conforme código acima, é possível omití-los conforme os exemplos abordados neste artigo.

Uma outra coisa interessante é que podemos combinar a utilização destas funções:

const resultado = dados
    .filter(a => a.id < 2)
    .map((a, i) => {
      return {
          id: a.id,
          index: i,
          nomeValor: a.nome + ' ' + a.valor
      };
    });
    
// Resultado:

[{
  id: 1,
  index: 0,
  nomeValor: "Nome 1 10"
}]

Links interessantes: