Menu

Map e Set no JavaScript: quando usar em vez de Object e Array

Entenda como Map e Set funcionam no JavaScript, as diferenças em relação a objetos e arrays comuns, e quando realmente vale a pena usá-los.

Duas coleções além de Object e Array

Objetos literais e arrays dão conta da maior parte do que um programa em JavaScript precisa, mas não foram pensados para tudo. Map e Set são coleções nativas que preenchem duas lacunas bem específicas: consultas por chave quando a chave não é uma string, e verificação de pertinência sem duplicatas.

Ambos existem desde o ES2015. Os dois são iteráveis, possuem a propriedade .size e funcionam bem com o spread operator. O modelo mental é direto:

  • Map — parecido com um objeto, só que as chaves podem ser de qualquer tipo e a ordem de inserção é preservada.
  • Set — parecido com um array, mas os valores são únicos e a busca é rápida.

Criando e usando um Map em JavaScript

Um Map guarda pares chave/valor. Para criar um, use new Map() e trabalhe com os métodos .set(), .get(), .has() e .delete():

index.js
Output
Click Run to see the output here.

Também dá para passar um array de pares [chave, valor] para o construtor e já inicializar o Map com dados:

index.js
Output
Click Run to see the output here.

Essa estrutura de array com dois elementos aparece o tempo todo quando se trabalha com Map — é assim que as entradas são representadas quando você itera sobre elas.

Map vs Object em JavaScript: vale a pena?

Objetos comuns parecem resolver o mesmo problema. E, na maioria das vezes, resolvem mesmo. Mas o Map elimina algumas chatices bem específicas:

index.js
Output
Click Run to see the output here.

Os objetos herdam de Object.prototype, então chaves como toString, constructor e hasOwnProperty já existem em qualquer objeto por padrão. Já o Map não carrega essa bagagem — as únicas chaves que existem são aquelas que você definiu.

Outras diferenças que vale a pena conhecer:

  • Qualquer tipo de chave. O Map aceita objetos, funções, números e booleanos como chave. Já o objeto converte silenciosamente chaves não-string para string: obj[1] e obj["1"] apontam para o mesmo lugar.
  • Ordem de inserção garantida. O Map itera na ordem em que as entradas foram adicionadas. Objetos também fazem isso na maior parte dos casos, mas chaves que parecem números são ordenadas primeiro — uma pegadinha sutil.
  • size embutido. map.size é O(1). No objeto você precisaria escrever Object.keys(obj).length, que reconstrói um array do zero.
  • Otimizado para alterações constantes. As engines otimizam o Map para adições e remoções frequentes. Já os objetos são otimizados para registros com formato estável.

Use um objeto quando estiver modelando um registro com chaves string conhecidas ({ name, email, age }). Use um Map quando as chaves forem dinâmicas, não forem strings, ou quando você for adicionar e remover entradas o tempo todo.

Como iterar um Map em JavaScript

O Map é iterável, ou seja, o for...of funciona direto nele e a desestruturação de cada entrada fica bem natural:

index.js
Output
Click Run to see the output here.

Se você precisa só das chaves ou só dos valores, use .keys() ou .values(). O .forEach() também está aí caso você prefira:

index.js
Output
Click Run to see the output here.

Para transformar um Map de volta em um objeto comum ou em um array, basta usar o spread:

index.js
Output
Click Run to see the output here.

Criando e usando um Set

O Set é uma estrutura que guarda apenas valores únicos. Se você tentar adicionar um valor que já existe, nada acontece:

index.js
Output
Click Run to see the output here.

A regra de unicidade segue o mesmo critério de igualdade do ===, com uma exceção curiosa: dentro de um Set, NaN é considerado igual a si mesmo, mesmo que NaN === NaN seja false em qualquer outro lugar.

Você pode passar um iterável para o construtor para já inicializar o Set com valores — e é daí que sai o famoso truque para remover duplicados de um array em JavaScript:

index.js
Output
Click Run to see the output here.

Uma linha, qualquer tipo primitivo. Para arrays de objetos isso não funciona — dois objetos diferentes com os mesmos campos continuam sendo dois valores distintos —, mas para strings, números e booleanos essa é a forma idiomática de remover duplicados.

Set vs Array: quando trocar

Tanto arrays quanto Sets guardam uma coleção de valores. Então, como decidir qual usar?

Use um Set quando:

  • Os valores precisam ser únicos e você quer que o próprio runtime garanta isso.
  • Você faz muitas verificações de pertencimento. set.has(x) é O(1); array.includes(x) é O(n). Dentro de um loop, essa diferença pesa rápido.
  • Basta a ordem de inserção. Sets iteram na ordem em que os valores foram inseridos, mas não permitem acesso por índice.

Fique com array quando:

  • Você precisa de acesso posicionalarr[0], fatiamento, ordenação.
  • Duplicatas fazem sentido — um carrinho de compras com duas unidades do mesmo item.
  • Você vai usar bastante método de array como .map, .filter, .reduce. Sets não têm esses métodos; você precisaria primeiro espalhar em um array.

Um exemplo rápido voltado para performance:

index.js
Output
Click Run to see the output here.

Se banned fosse um array, cada callback do filter teria que varrer a lista inteira. Como Set, cada consulta é feita em tempo constante.

Iterando sobre um Set

Funciona igual ao Map — dá pra usar for...of sem complicação, e com spread você já obtém um array:

index.js
Output
Click Run to see the output here.

Sets também oferecem .keys(), .values() e .entries() por simetria com o Map, mesmo que, no caso do Set, chaves e valores sejam a mesma coisa. Na prática, quase sempre você vai iterar direto.

Exemplo prático: contando visitantes únicos por página

Juntando os dois — um Map que associa caminhos de página a um Set de IDs de visitantes:

index.js
Output
Click Run to see the output here.

O Map cuida do mapeamento entre o caminho e o bucket; o Set cuida de evitar duplicatas dentro de cada bucket. Dá pra fazer a mesma coisa com um objeto comum e arrays, mas aí você acaba enchendo o código de indexOf e hasOwnProperty pra se proteger o tempo todo.

WeakMap e WeakSet, rapidinho

Existem duas coleções relacionadas para um caso de uso bem específico: WeakMap e WeakSet. Elas guardam referências de forma fraca, ou seja, quando uma entrada cuja chave (no WeakMap) ou valor (no WeakSet) não tem mais nenhuma outra referência apontando pra ela, o garbage collector limpa automaticamente.

index.js
Output
Click Run to see the output here.

Elas só aceitam objetos como chaves, não são iteráveis e não têm .size. Isso é proposital — se fossem iteráveis, o garbage collector ficaria observável. São úteis pra guardar metadados sobre objetos que você não controla, mas raramente aparecem no dia a dia.

A seguir: JSON

Map e Set funcionam muito bem em memória, mas nenhum dos dois sobrevive a um JSON.stringify: Maps viram [] e Sets também viram []. A próxima página é sobre JSON — como serializar e fazer parse dos dados, além dos padrões pra lidar com as coleções apresentadas aqui quando elas precisam atravessar a rede ou ser salvas em arquivo.

Perguntas frequentes

Qual a diferença entre Map e Object no JavaScript?

Um Map aceita qualquer valor como chave — objetos, funções, números, o que você quiser —, enquanto um objeto converte as chaves para string (ou symbol). O Map ainda expõe o tamanho direto em .size, itera na ordem de inserção e não herda chaves do prototype, então você não corre o risco de colidir com toString ou constructor. Use Map quando as chaves não forem strings ou quando você precisar adicionar e remover entradas com frequência.

Para que serve um Set no JavaScript?

O Set guarda apenas valores únicos — duplicatas são ignoradas sem reclamar. A forma mais rápida de remover duplicados de um array é [...new Set(arr)]. Outra vantagem é o .has() com custo O(1), bem mais rápido que array.includes() quando você precisa testar se um valor existe dentro de um loop.

Como iterar sobre um Map?

Dá para usar for...of direto: for (const [key, value] of myMap) já desestrutura cada entrada. Você também pode percorrer myMap.keys(), myMap.values() ou myMap.entries(). A ordem de iteração sempre segue a ordem de inserção, algo que objetos comuns não garantem quando as chaves parecem números.

Aprenda a programar com o Coddy

COMEÇAR