Transforme arrays em ouro: aprenda a manipular dados com o map() em Javascript
Tags: map, javascript, array, githubbrasil
Published: 2023-10-09 11:21:13

## Descubra como usar o map() em Javascript. Manipule arrays e transforme dados com esse método essencial para qualquer pessoa desenvolvedora.
 
**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:
 
```javascript
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:
 
```javascript
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](https://demenezes.dev/_next/image/?url=https%3A%2F%2Fwww.datocms-assets.com%2F64896%2F1696506831-o-mapa-da-regiao-sul-do-brasil-onde-cada-estado-aponta-para-aquela-regiao-fisica-na-vida-real.png%3Fauto%3Dformat&w=3840&q=75)
 
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](https://demenezes.dev/_next/image/?url=https%3A%2F%2Fwww.datocms-assets.com%2F64896%2F1696506854-um-sumario-e-uma-seta-apontando-para-um-livro.jpg%3Fauto%3Dformat&w=3840&q=75)
 
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?
 
```javascript
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:
 
```javascript
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](https://demenezes.dev/posts/como-usar-foreach-javascript-exemplos/#o-que-o-foreach-retorna). 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:
 
```javascript
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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) e escrever o mesmo código de exemplo assim:
 
```javascript
const uppercaseNames = lowercaseNames.map(name => name.toUpperCase());
```
 
Veja agora mais exemplos com o map, bastante comuns no meio corporativo.
 
## Oi, eu sou o Goku!

{% embed https://www.youtube.com/watch?v=xbJB71BBfKA&ab_channel=KenniKlein %}
 
"Exemplo do mundo corporativo" apenas se você trabalhar com o Akira Toriyama.
 
![Escrita "laugh" em letra neon (risada em inglês)](https://demenezes.dev/_next/image/?url=https%3A%2F%2Fwww.datocms-assets.com%2F64896%2F1653006346-tim-mossholder-imld5dbclm4-unsplash-1.jpg%3Fauto%3Dformat&w=3840&q=75)
 
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:
 
```javascript
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`:
 
```javascript
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`:
 
```javascript
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:
 
```javascript
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:
 
```javascript
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`:
 
```javascript
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:
 
```javascript
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:
 
```javascript
[
  { 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:
 
```javascript
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**.
 
```javascript
characters.map(function(character, index) {
  return index;
});
```
 
O código acima não é útil para nada, além de compreender o `index`:
 
```javascript
[ 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:
 
```javascript
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](https://demenezes.dev/posts/como-usar-foreach-javascript-exemplos/#o-par%C3%A2metro-index-da-funcao-callback).
 
E o resultado é:
 
```javascript
[
  'é 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`:
 
```javascript
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. 
 
```javascript
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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) 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:
 
```javascript
[
  { 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:
 
```javascript
characters.map(function(character, index, array) {
  // ...
});
```
 
A primeira coisa a fazer é descobrir a raça de um personagem:
 
```javascript
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`.<br>Então vou criar uma variável para contar quantas vezes essa raça aparece:
 
```javascript
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:
 
```javascript
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.
 
```javascript
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 é:
 
```javascript
[
  { 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](https://demenezes.dev/_next/image/?url=https%3A%2F%2Fwww.datocms-assets.com%2F64896%2F1696506890-um-menino-com-um-mapa-na-mao.jpg%3Fauto%3Dformat&w=3840&q=75)
 
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:
 
- [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
- [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
- [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax)