Map e Set são estruturas de dados introduzidas no ES6 que ampliam as capacidades de coleções em JavaScript. Enquanto objetos e arrays são amplamente utilizados, Map e Set oferecem vantagens específicas em termos de desempenho, segurança e semântica. Nesta aula, vamos explorar cada um deles em detalhes, entender seus casos de uso e aprender a utilizá-los corretamente.

Map é uma coleção de pares chave-valor onde as chaves podem ser de qualquer tipo, incluindo objetos, funções e tipos primitivos. Diferente de objetos, Map preserva a ordem de inserção e fornece métodos específicos para iteração e manipulação. Set, por sua vez, é uma coleção de valores únicos, eliminando automaticamente duplicatas. Ambos são iteráveis e possuem tamanho dinâmico.

Quando usar Map vs objeto

Objetos em JavaScript são mapas de strings para valores, mas possuem limitações: as chaves são sempre strings (ou símbolos), não há garantia de ordem de inserção (embora na prática seja mantida), e a herança prototipal pode causar conflitos. Map resolve esses problemas permitindo chaves de qualquer tipo, mantendo a ordem de inserção e sem herança indesejada.

Use Map quando: precisar de chaves que não sejam strings (como objetos), houver necessidade de iteração frequente (Map possui métodos como keys(), values(), entries()), ou quando o número de pares chave-valor for dinâmico e houver muitas operações de adição/remoção. Use objetos quando a estrutura for simples, com chaves conhecidas em tempo de escrita, e quando precisar de funcionalidades como JSON.stringify (que não funciona diretamente com Map).

// Exemplo: Map com chave objeto
const user1 = { id: 1 };
const user2 = { id: 2 };
const map = new Map();
map.set(user1, "Alice");
map.set(user2, "Bob");
console.log(map.get(user1)); // "Alice"

// Objeto não pode ter chave objeto diretamente
const obj = {};
obj[user1] = "Alice"; // chave será convertida para string "[object Object]"
console.log(obj[user1]); // "Alice" (mas sobrescreve se outro objeto for usado)

Set e unicidade

Set é uma coleção de valores únicos. Qualquer tipo de valor pode ser armazenado, e valores duplicados são automaticamente ignorados. A unicidade é determinada pela comparação SameValueZero, similar a ===, mas considerando NaN igual a si mesmo.

Set é útil para eliminar duplicatas de arrays, verificar existência de elementos de forma eficiente (complexidade O(1) em média) e realizar operações de conjunto como união, interseção e diferença. Set também é iterável, permitindo uso com for...of e spread operator.

// Criando um Set a partir de um array com duplicatas
const array = [1, 2, 2, 3, 4, 4, 5];
const set = new Set(array);
console.log([...set]); // [1, 2, 3, 4, 5]

// Métodos principais
set.add(6);
set.delete(2);
console.log(set.has(3)); // true
console.log(set.size); // 5

// Iteração
for (const value of set) {
  console.log(value);
}

WeakMap/WeakSet (introdução)

WeakMap e WeakSet são versões especiais de Map e Set que permitem que seus objetos-chave sejam coletados pelo garbage collector se não houver outras referências para eles. Isso significa que as chaves em WeakMap e os valores em WeakSet são mantidos de forma fraca: se a única referência a um objeto for como chave do WeakMap, ele pode ser removido da memória.

WeakMap e WeakSet não são iteráveis, não possuem métodos como keys(), values(), entries(), nem a propriedade size. Eles são úteis para armazenar metadados associados a objetos sem impedir que esses objetos sejam coletados, como em cache ou listeners de eventos.

// WeakMap: chave deve ser objeto
const wm = new WeakMap();
let obj = {};
wm.set(obj, "informação privada");
console.log(wm.get(obj)); // "informação privada"
obj = null; // objeto pode ser coletado, e a entrada no WeakMap é removida

// WeakSet: valores devem ser objetos
const ws = new WeakSet();
let obj2 = {};
ws.add(obj2);
console.log(ws.has(obj2)); // true
obj2 = null; // objeto pode ser coletado

Boas práticas

Ao trabalhar com Map e Set, prefira métodos específicos como has em vez de includes em arrays para verificar existência, pois são mais eficientes. Para operações de conjunto, crie funções utilitárias que usam Set. Evite usar objetos como chaves em Map se eles forem criados dinamicamente sem controle de referência, pois isso pode causar vazamento de memória se não forem removidos.

Referências

Exercícios

  1. Crie um Map chamado idades que associe nomes (strings) a idades (números). Adicione três entradas: 'Ana' com 25, 'João' com 30 e 'Maria' com 22. Depois, exiba a idade de João.

    ✓ Resposta:
    const idades = new Map();
    idades.set('Ana', 25);
    idades.set('João', 30);
    idades.set('Maria', 22);
    console.log(idades.get('João')); // 30
    
  2. Dado o array [1, 2, 2, 3, 4, 4, 5], use um Set para remover as duplicatas e retornar um novo array com valores únicos.

    ✓ Resposta:
    const array = [1, 2, 2, 3, 4, 4, 5];
    const unicos = [...new Set(array)];
    console.log(unicos); // [1, 2, 3, 4, 5]
    
  3. Escreva uma função que receba dois arrays e retorne um array contendo apenas os elementos que estão presentes em ambos (interseção). Use Set para otimizar.

    ✓ Resposta:
    function intersecao(arr1, arr2) {
      const set2 = new Set(arr2);
      return arr1.filter(item => set2.has(item));
    }
    console.log(intersecao([1,2,3], [2,3,4])); // [2,3]
    
  4. Explique por que WeakMap não possui a propriedade size e não é iterável. Dê um exemplo de uso prático.

    ✓ Resposta:

    WeakMap não expõe size nem é iterável porque as referências fracas podem ser coletadas a qualquer momento, tornando o tamanho dinâmico e imprevisível. Um exemplo de uso é armazenar metadados associados a objetos DOM sem impedir que eles sejam removidos da memória:

    const wm = new WeakMap();
    const elemento = document.getElementById('meuBotao');
    wm.set(elemento, { cliques: 0 });
    // Quando o elemento for removido do DOM, a entrada no WeakMap pode ser coletada.
    
  5. Crie um WeakSet que armazene objetos. Adicione dois objetos e verifique se um deles está presente. Depois, remova a referência a um dos objetos e explique o que acontece com a entrada no WeakSet.

    ✓ Resposta:
    const ws = new WeakSet();
    let obj1 = { a: 1 };
    let obj2 = { b: 2 };
    ws.add(obj1);
    ws.add(obj2);
    console.log(ws.has(obj1)); // true
    obj1 = null; // obj1 não é mais referenciado, então a entrada no WeakSet pode ser removida pelo garbage collector
    // Não há como verificar após a coleta, mas o WeakSet não impede a coleta.