Executando verificação de segurança...
5

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

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.

uma mão inserindo alfinetes em um mapa

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"?

Fiz um post completo no meu blog com toda a explicação de como usar o map em 90% dos casos.

Vou deixar o link aqui nas fontes :D

Se puder, me dê um feedback de o que achou do contéudo!

Carregando publicação patrocinada...
8

Muito bom! Complementando, acho que a principal lição é que map sempre retorna outro array, então se vc não precisa desse outro array, não deveria usar map.

Tenho visto muitas pessoas usando map indevidamente como substituto de for/forEach, o que apesar de "funcionar", é um uso torto. Por exemplo:

const array = [1,2,3,4];

// ATENÇÃO: uso indevido de map, pois um for seria mais adequado
array.map(n => console.log(n * 2));

O código até "funciona" (imprime os valores desejados), mas é um uso torto por vários motivos:

  • map retorna outro array, que no caso não foi usado pra nada. Ou seja, gerou outro array à toa.
  • semanticamente fica confuso, pois o uso de map sugere que os valores serão mapeados e deseja-se ter/usar o outro array com os resultados. Mas como não foi isso que aconteceu, acaba causando confusão, principalmente se outras pessoas forem dar manutenção no código ("Por que não usou um for?")

Neste caso seria muito mais adequado um loop:

for (const n of array) {
    console.log(n * 2);
}

Ou o forEach:

array.forEach(n => console.log(n * 2));

Lembrando ainda que forEach (assim como map, filter, reduce, etc) recebe uma função de callback que é executada para cada elemento. Claro que para poucos arrays pequenos não faz diferença, mas tem que lembrar que chamadas de função têm seu custo, que pode ou não ser desprezível dependendo do caso.


Quanto a um dos seus exemplos, vale lembrar que ao retornar o mesmo objeto, uma alteração neste causa uma mudança em todos os arrays:

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' }
];

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

// nome "Goku" não foi alterado
console.log(mapeado[0]); // { name: 'Goku', race: 'saiyajin' }

// muda o nome no array original
characters[0].name = 'GOKU';

// a alteração se reflete no outro array, já que ele tem uma referência para o mesmo objeto
console.log(mapeado[0]); // { name: 'GOKU', race: 'saiyajin' }

Como o objeto referente a "Goku" é o mesmo nos dois arrays, qualquer alteração feita em um reflete no outro.

Para evitar isso, teria que retornar uma cópia mesmo, então bastaria mudar para:

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

Lembrando que neste caso estou fazendo uma shallow copy, ou seja, se o objeto character tiver outros objetos dentro ele, o problema persistiria. Neste caso, teria que ser feita uma deep copy (dê uma olhada aqui para mais detalhes).

Por fim, a função também poderia ser assim:

const mapeado = characters.map(character => {
    const novo = { ...character };
    if (character.race === 'terráqueo')
        novo.name = character.name.toUpperCase();

    return novo;
});

Esta abordagem seria mais vantajosa caso cada objeto tivesse muitas propriedades, pois aí vc já copia todas de uma vez. Neste caso não faz tanta diferença porque são só duas (name e race), mas se tivesse mais, seria bem massante ficar copiando uma a uma.

4

Sabe o que é engraçado? Na parte subjetiva, o erro vai se perpetuando tanto que fica melhor assim :)

Por exemplo:

principalmente se outras pessoas forem dar manutenção no código ("Por que não usou um for?")

As pessoas usam tanto errado, que "ninguém" mais pergunta por que não usou o for :D O que obviamente, estritamente o map() continua sendo a pior solução, mas as pessoas param de questionar. E aí vem a frase que sempre uso: quando você treina o erro é ele que fará sempre. E vai brigar que ele é o certo.

Para quem faz questão de uma linha:

for (const n of array) console.log(n * 2);

Ah, mas é mais longo. Objetivo do código não é ter menos caracteres, é de produzir o resultado certo da melhor forma possível. Esta forma é semanticamente mais correta e mais eficiente.

Com forEach() não é tão errado, mas ainda é menos eficiente, e ainda é uma desnecessidade. O que não é necessário nunca deveria ser usado.

Os outros exemplos mostram como usar + map() facilmente se torna mais comploicado e contém armadilhas. Já viu os malabarismos que o pessoal faz porque precisava de um continue que obviamente não tem no map()?

Espero ter ajudado.


Farei algo que muitos pedem para aprender a programar corretamente, gratuitamente. Para saber quando, me segue nas suas plataformas preferidas. Quase não as uso, não terá infindas notificações (links aqui).

1

Vi os exemplos que vc colocou, e também já ouvi muito falar que o for clássico é mais performático que forEach ou map.

Mas acho que a diferença no fim das contas é mto pouca.

Eu particularmente prefiro usar a programação funcional, então evito o for pois acho a escrita dele mais complicada.

1

Perfeito!

Eu menciono no final do post que o map sempre retorna uma array, e caso vc não precisa de uma, o map não é a melhor ferramenta.

No exemplo dos consoles que tu deu, costumo usar o forEach, acho mais prático que o for tradicional.


"...vale lembrar que ao retornar o mesmo objeto, uma alteração neste causa uma mudança em todos os arrays."

Isso daqui eu não sabia, eu achava que o map retornava ou array completamente novo.

Achei a solução de retornar um spread operator do objeto ótima, já que ela cria outro objeto para retornar, em vez de retornar exatamente o que recebeu.

É por isso que esse problema não acontece com objetos alterados no map. Fiz um teste com posição 2, 'kuririn'. Pq assim cai no if e retorna um novo objeto que construí ali dentro:

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' }
];

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

// nome "kuririn" foi alterado para 'KURIRIN'
console.log(mapeado[2]); // { name: 'KURIRIN', race: 'terráqueo' }

// muda o nome no array original para 'GOKU'
characters[2].name = 'GOKU';

// a alteração não se reflete no outro array
console.log(mapeado[2]); // { name: 'KURIRIN', race: 'terráqueo' }

Obrigado pelo comentário :D