Transforme arrays em ouro: aprenda a manipular dados com o map() em Javascript

Descubra como usar o map() em Javascript. Manipule arrays e transforme dados com esse método essencial para qualquer pessoa desenvolvedora.

  • map
  • javascript
  • array
Front-end

O map é um método Javascript que cria uma array a partir de outra, sem alterar a original.

Fim.

Isso é tudo que você precisa saber. Daqui em diante o que você vai ler é apenas essa explicação inicial em detalhes.

Veja como gerar uma array de strings em caixa alta, a partir de outra em caixa baixa:

const lowercaseNames = [ 'goku', 'bulma', 'kuririn' ];

const uppercaseNames = lowercaseNames.map(function(name) {
  return name.toUpperCase();
});

console.log(uppercaseNames); // [ 'GOKU', 'BULMA', 'KURIRIN' ]

E agora, como gerar uma array com o dobro dos números da array original:

const numbers = [ 5, 8, 3, 6, 7, 2, 5 ];

const double = numbers.map(function(number) {
  return number * 2;
});

console.log(double); // [ 10, 16, 6, 12, 14, 4, 10 ]

Sempre explico alguns termos da programação aqui no blog com a tradução dos comandos.

Mas que diabos é "mapear"?

Por que "map"?

"Mapear" é como criar um mapa que represente algo.

Veja um exemplo. O mapa do Brasil mostra todos os estados e o Distrito Federal. O desenho de cada estado no papel representa aquela região física no Brasil. É como se o mapa fosse uma lista de itens (estados), e cada um deles apontasse para outra coisa em outro lugar:

  • O desenho do estado do Rio Grande do Sul no mapa representa a região do Rio Grande do Sul na vida real

  • O desenho do estado de Santa Catarina no mapa representa a região de Santa Catarina na vida real

  • O desenho do estado do Paraná no mapa representa a região do Paraná na vida real

A região física do país Brasil gera um mapa (desenho em um papel) para representar ela.

o mapa da região sul do brasil, onde cada estado aponta para aquela região física na vida real

O mesmo acontece com um livro. Se você dividir todo o seu conteúdo em uma lista de capítulos, é possível gerar um mapa com o nome do capítulo e a página onde ele começa. Esse mapa se chama sumário.

Isso significa que cada item do sumário representa um capítulo do livro.

um sumário e uma seta apontando para um livro

E isso é bem parecido com o que o map do Javascript faz. Ele usa uma array original (a região do país ou os capítulos do livro) para gerar uma nova array (o mapa ou o sumário).

Importante: esses exemplos são só exemplos. Você pode considerar o mapa do Brasil como um resumo da região do país Brasil. Pode ver o sumário como um resumo dos capítulos de um livro. Mas o map não gera uma "array resumida", ignore esse ponto.

Agora você já sabe o que o map faz: cria uma array a partir de outra, sem alterar a original.

E o que o map não faz?

O map faz um clone ou transforma uma array?

Tecnicamente não!

Não é certo afirmar que ele faz um clone, porque a intenção não é criar outra array igual, apesar de isso ser possível.

Também não posso dizer que o map transforma uma array em outra, pois ele não altera a original. Isso é a imutabilidade, um dos pilares da programação funcional.

Você pode modificar (ou não) cada item da lista ao colocá-la na nova. Como característica, o map sempre gera uma array com a mesma quantidade de itens que a original. Se você quer criar uma array com menos itens, precisará usar o método filter, mas isso fica para outro artigo.

Agora vamos para a prática.

Veja como fazer o seu primeiro map

Você se lembra do exemplo que dei no início do post?

const lowercaseNames = [ 'goku', 'bulma', 'kuririn' ];

const uppercaseNames = lowercaseNames.map(function(name) {
  return name.toUpperCase();
});

console.log(uppercaseNames); // [ 'GOKU', 'BULMA', 'KURIRIN' ]

Primeiro, criei uma array com vários nomes em caixa baixa (lowercase). Depois, usei o lowercaseNames.map(...) para gerar a nova array, e o resultado disso coloquei na const uppercaseNames.

O map recebe como argumento uma função callback. Declarei essa função assim:

function(name) {
  return name.toUpperCase();
}

Essa função callback vai:

  • Executar uma vez para cada item da array original

  • Realizar uma operação com esse item. No caso, transformar em caixa alta com o método de string .toUpperCase()

  • E colocar o resultado na array nova através do return

Abaixo veja o passo a passo de como isso acontece:

Antes de começar:

- Array original: [ 'goku', 'bulma', 'kuririn' ]
- Array final: []

Primeira iteração:

- Valor original: 'goku'
- Valor retornado: 'GOKU'
- Array final: [ 'GOKU' ]

Segunda iteração:

- Valor original: 'bulma'
- Valor retornado: 'BULMA'
- Array final: [ 'GOKU', 'BULMA' ]

Terceira iteração:

- Valor original: 'kuririn'
- Valor retornado: 'KURIRIN'
- Array final: [ 'GOKU', 'BULMA', 'KURIRIN' ]

Resultado final do map:

- [ 'GOKU', 'BULMA', 'KURIRIN' ]

Assim, cada iteração retorna um valor que vai montando a array final.

Mas será que você é obrigado a declarar esse return?

A palavra return dentro do map

"E sE eU nÃo CoLoCaR rEtUrN?"

Você deveria hehe pois é assim que o map funciona.

Se você não leu um dos últimos posts aqui do blog, eu contei uma história com exemplos de como usar o forEach. Lá eu expliquei que toda função retorna alguma coisa. E quando você não define qual é, a função retorna undefined.

Então respondendo à pergunta, se o map não retorna nada, ele formará a nova array apenas com undefined. E imagino que não seja essa sua intenção:

const lowercaseNames = [ 'goku', 'bulma', 'kuririn' ];

const uppercaseNames = lowercaseNames.map(function(name) {
  const result = name.toUpperCase();
  // sem return
});

console.log(uppercaseNames); // [ undefined, undefined, undefined ]

O map precisa de um retorno, mas não necessariamente da palavra return.

Você pode usar uma arrow function e escrever o mesmo código de exemplo assim:

const uppercaseNames = lowercaseNames.map(name => name.toUpperCase());

Veja agora mais exemplos com o map, bastante comuns no meio corporativo.

Oi, eu sou o Goku!

"Exemplo do mundo corporativo" apenas se você trabalhar com o Akira Toriyama.

Escrita "laugh" em letra neon (risada em inglês)
Humor e piadas

Mesmo assim, espero que isso ajuda você a entender o funcionamento do map em Javascript.

Abaixo, você vai ver uma array com os personagens de Dragon Ball e suas raças:

const characters = [
  { name: 'Goku',        race: 'saiyajin'     },
  { name: 'Cell',        race: 'android'      },
  { name: 'Kuririn',     race: 'terráqueo'    },
  { name: 'Bulma',       race: 'terráqueo'    },
  { name: 'Vegeta',      race: 'saiyajin'     },
  { name: 'Mestre Kame', race: 'terráqueo'    },
  { name: 'Kami-Sama',   race: 'namekuseijin' }
];

A partir de agora, vou mostrar 3 formas de usar o map em Javascript com essa array. Em cada uma delas, vou adicionar um parâmetro a mais, para aumentar o nível de complexidade aos poucos.

O primeiro parâmetro do map: item

Ele representa cada item da array original.

Você pode chamar os parâmetros como quiser, mas dar bons nomes para as coisas ajuda demais. Costumo chamá-lo item, algo bem genérico, quando não sei o que tem na array. No primeiro exemplo do post eu usei name ou number.

Aqui, vou chamar de character:

characters.map(function(character) {
  ...
});

Vou mudar o nome dos personagens para caixa alta. Mas não de todos eles, e sim apenas dos personagens terráqueos.

Para isso, preciso descobrir a raça de cada personagem antes através do character.race:

characters.map(function(character) {
  if (character.race === 'terráqueo') {
    // mudar o nome para caixa alta
  } else {
    // retornar dados originais
  }  
});

Agora é preciso dar atenção a três pontos:

  1. Não esquecer do return

  2. Colocar o nome em caixa alta

  3. Caso não seja terráqueo, retornar as informações originais

O if terá um return, e o else terá outro, porque o valor que quero retornar depende da raça. Cada um deles retorna um objeto com as novas informações do personagem. Por enquanto, vou deixar esses objetos iguais para você entender passo a passo:

characters.map(function(character) {
  if (character.race === 'terráqueo') {
    return {
      name: character.name,
      race: character.race
    };
  } else {
    return {
      name: character.name,
      race: character.race
    };
  }  
});

Como quero alterar o nome dos terráqueos, dentro do if eu adiciono o .toUpperCase() no nome, e não altero a raça:

characters.map(function(character) {
  if (character.race === 'terráqueo') {
    return {
      name: character.name.toUpperCase(),
      race: character.race
    };
  } else {
    return {
      name: character.name,
      race: character.race
    };
  }  
});

Dentro do else, o objeto que eu retornei é exatamente igual ao que recebi. Então posso simplificar apenas com return character:

characters.map(function(character) {
  if (character.race === 'terráqueo') {
    return {
      name: character.name.toUpperCase(),
      race: character.race
    };
  } else {
    return character;
  }
});

Se eu remover o else, tornar a função callback em uma arrow function e tirar as chaves do if, posso simplificar mais ainda:

characters.map(character => {
  if (character.race === 'terráqueo') return {
    name: character.name.toUpperCase(),
    race: character.race
  };
  
  return character;  
});

Simplificar código é um vício, mas estou me tratando (mentira).

Como o map alterou apenas os nomes de Kuririn, Bulma e Mestre Kame, o resultado é esse:

[
  { name: 'Goku', race: 'saiyajin' },
  { name: 'Cell', race: 'android' },
  { name: 'KURIRIN', race: 'terráqueo' },
  { name: 'BULMA', race: 'terráqueo' },
  { name: 'Vegeta', race: 'saiyajin' },
  { name: 'MESTRE KAME', race: 'terráqueo' },
  { name: 'Kami-Sama', race: 'namekuseijin' }
]

Veja agora um exemplo com mais um parâmetro do map.

O segundo parâmetro do map: index

Veja esse código:

characters.map(function(character, index) {
  // ...
});

Perceba que surgiu o parâmetro index. Essa variável inicia em zero, e aumenta em 1 a cada iteração do loop.

characters.map(function(character, index) {
  return index;
});

O código acima não é útil para nada, além de compreender o index:

[ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]

O último index é igual à quantidade de itens do array menos 1. Se a array tem 9 itens, o index vai de 0 a 8 (9 - 1 = 8).

Ok, e agora? O que faço eu da vida?

Parafraseando Tropa de Elite 2, o desafio agora é outro. Quero adicionar em cada personagem uma nova propriedade que informa se seu index é par ou impar.

Vou explicar por partes:

characters.map(function(character, index) {
  if (index % 2 === 0) {
    return 'é par';
  }
  
  return 'é impar';
});

Caso você não conheça essa técnica, para descobrir se um número é par ou impar, você precisa fazer a operação módulo com o número 2. Se o resultado for zero, ele é par. Se for 1, ele é impar. Expliquei em detalhes nesse capítulo do post anterior sobre forEach.

E o resultado é:

[
  'é par',
  'é impar',
  'é par',
  'é impar',
  'é par',
  'é Impar',
  'é par',
  'é impar',
  'é par'
]

Então agora posso fazer a mesma coisa de antes, vou retornar um valor diferente dependendo do index:

characters.map(function(character, index) {
  if (index % 2 === 0) {
    return {
      name: character.name,
      race: character.race,
      evenOrOdd: 'par'
    };
  }
  
  return {
    name: character.name,
    race: character.race,
    evenOrOdd: 'impar'
  };
});

Vou simplificar o código novamente.

characters.map((character, index) => ({
  ...character,
  evenOrOdd: index % 2 === 0 ? 'par' : 'impar'
}));

Usei uma arrow function que retorna diretamente as informações do personagem. A linha ...character usa o operador Spread para "espalhar" as informações originais do objeto que recebi. E na linha seguinte apenas adiciono o valor de evenOrOdd.

E o resultado fica assim:

[
  { name: 'Goku', race: 'saiyajin', evenOrOdd: 'par' },
  { name: 'Cell', race: 'android', evenOrOdd: 'impar' },
  { name: 'Kuririn', race: 'terráqueo', evenOrOdd: 'par' },
  { name: 'Bulma', race: 'terráqueo', evenOrOdd: 'impar' },
  { name: 'Vegeta', race: 'saiyajin', evenOrOdd: 'par' },
  { name: 'Mestre Kame', race: 'terráqueo', evenOrOdd: 'impar' },
  { name: 'Kami-Sama', race: 'namekuseijin', evenOrOdd: 'par' }
]

Ficou com alguma dúvida? Volte e execute os códigos acima com alguns consoles, para entender o que acontece em cada linha.

Agora mais um exemplo com o último parâmetro do map.

O terceiro parâmetro do map: array

O único parâmetro estático.

Diferente os parâmetros anteriores, esse não muda a cada iteração. Ele sempre retorna a array original que você está percorrendo.

Não recomendo você usar esse parâmetro para alterar a array dentro do map, e sim consultá-la para fazer outra operação.

O desafio agora é: caso o personagem seja o único daquela raça dentro da array, insira a string Super antes do seu nome.

É possível ver que apenas as raças Android e Namekuseijin possuem um único personagem. Então já dá para ter uma noção de como ficará o resultado.

O código vai começar como nos outros exemplos:

characters.map(function(character, index, array) {
  // ...
});

A primeira coisa a fazer é descobrir a raça de um personagem:

characters.map(function(character, index, array) {
  const race = character.race;
});

Agora: como descobrir quantos personagens existem na array com essa raça?

Isso poderia ser feito com outros métodos específicos, como o reduce. Mas vou mostrar de forma mais imperativa, para simplificar, pois esse não é o objetivo principal. No futuro farei um post para explicar ele em mais detalhes.

Quero alterar apenas nomes de personagens com raças únicas. Logo, a raça de cada personagem do loop deve aparecer na array apenas 1 vez (lembre disso). Ou seja, haverá uma verificação algumValor === 1.
Então vou criar uma variável para contar quantas vezes essa raça aparece:

characters.map(function(character, index, array) {
  const race = character.race;
  
  let raceQuantity = 0;
});

Obviamente ela começa com o valor 0.

Depois vou usar o forloop para percorrer a array, e adicionar 1 a essa variável, sempre que encontrar outra raça igual:

characters.map(function(character, index, array) {
  const race = character.race;
  
  let raceQuantity = 0;

  let i;
  for (i = 0; i < array.length; i++) {
    if (array[i].race === race) {
      raceQuantity = raceQuantity + 1;
    }
  }
});

A variável raceQuantity nunca será 0, porque sempre existirá pelo menos um personagem daquela raça na array.

Então eu verifico se essa variável é igual a 1 (lembrou?), ou seja, aquela raça aparece apenas uma vez na array. Então, adiciono o 'Super ' antes do nome. Caso contrário, apenas retorno os dados originais do personagem.

characters.map(function(character, index, array) {
  const race = character.race;
  
  let raceQuantity = 0;

  let i;
  for (i = 0; i < array.length; i++) {
    if (array[i].race === race) raceQuantity++;
  }
  
  if (raceQuantity === 1) {
    return {
      name: 'Super ' + character.name,
      race: character.race
    };
  }
  
  return character;
});

E o resultado é:

[
  { name: 'Goku', race: 'saiyajin' },
  { name: 'Super Cell', race: 'android' },
  { name: 'Kuririn', race: 'terráqueo' },
  { name: 'Bulma', race: 'terráqueo' },
  { name: 'Vegeta', race: 'saiyajin' },
  { name: 'Mestre Kame', race: 'terráqueo' },
  { name: 'Super Kami-Sama', race: 'namekuseijin' }
]

Longe de mim dizer que, agora, você já sabe tudo sobre o método map em Javascript. Mas arrisco dizer que agora você consegue resolver pelo menos 90% dos problemas desse tipo.

Legal:

  • Agora você fecha essa aba do seu navegador...

  • Vai fazer outra coisa...

  • Dorme...

E amanhã encontra um problema qualquer, sem saber que o map é a solução. Então agora quero mostrar como descobrir se o map é a ferramenta certa para o seu problema.

Em quais situações você vai precisar usar o map

um menino com um mapa na mão

Não adianta de nada ter um martelo e não saber que, além de pregar, ele também serve para remover pregos. Da mesma forma, não adianta saber toda teoria que expliquei, se no dia a dia você não sabe quando usar. Então quero mostrar como eu penso para concluir que devo usar o método map.

O que eu quero (output)?

Se o que eu quero for uma array, o map está entre as ferramentas possíveis.

Isso porque o que o map te dá (o retorno ou resultado dele) é uma array.

Se você precisa de algo que não é uma array, esqueça o map.

O que eu tenho (input)?

O que tenho em mãos para gerar uma array?

Se a resposta for "eu tenho uma array", sim, o map está novamente entre as opções.

Lembre-se: ele gera uma array a partir de outra. Se você tem uma string ou objeto, vai precisar primeiro converter isso em uma array.

Qual a quantidade de itens da nova array?

A resposta sempre deve ser a mesma quantidade de itens da array original.

Conforme comentei no começo do artigo, o map gera uma array com a mesma quantidade de itens da original. Se a quantidade for maior ou menor, descarte o uso do map. Nesse caso, procure pelo filter, reduce ou até o find.

É possível fazer um mapeamento ou apontamento?

É possível que cada item da minha array atual represente algo na nova array?

Se os dados das duas arrays (a original e a nova) estão relacionados, é possível fazer esse mapeamento.

Um exemplo possível seria pegar uma array de nomes completos, e retornar uma array apenas com os últimos sobrenomes.

Um exemplo impossível seria puxar uma array de produtos do banco de dados, e gerar uma array de clientes que já compraram esse produto. Isso porque cada produto não aponta para apenas um cliente, esses dados são entidades diferentes.

Com esse pensamento, acredito que rapidinho você vai descobrir quando usar o map de forma automática. Hoje em dia ainda penso nesses tópicos, eles nunca me abandonaram.

Callback

O método map em Javascript cria uma array a partir de outra, sem alterar a original.

Você pode por exemplo:

  • Criar uma array de strings em caixa alta a partir de outra em caixa baixa

  • Criar uma array de números resultados de uma operação com os números de outra array

  • Etc

A palavra "map" foi escolhida para esse comando, porque o que ele faz é um mapeamento. Ou seja, cria uma lista de itens onde cada um deles represente um item na lista original.

O map não clona nem transforma nada, ele cria uma array a partir de outra.

É obrigatório retornar um valor dentro da função callback. Caso contrário, o seu map irá te devolver uma lista de undefined.

O primeiro parâmetro da função callback é o item da array original, e ele altera a cada loop.

O segundo parâmetro da função callback é o index. Ele inicia em 0 e aumenta em 1 a cada loop.

O terceiro parâmetro da função callback é a própria array original. Use ela apenas como consulta, não para alterá-la.

Quando se ver em um problema no código, use o raciocínio que mostrei no final:

  • Se o que você quer é uma array

  • Se o que você tem é uma array

  • Se elas têm a mesma quantidade de itens

  • E se existe uma relação/pareamento entre os dados delas

Provavelmente o map vai resolver o seu problema.

Mais uma vez posso dizer aliviado: eu gostaria MUITO de ter lido uma explicação assim quando comecei a estudar Javascript. Espero ter explicado da melhor forma para você esse tipo de conteúdo.

Caso algo não tenha ficado claro, deixe nos comentários que será o maior prazer te responder.

Obrigado pela sua leitura 😄 um abraço.


Continue estudando:

Veja outros posts sobre Front-end