Map e Set: coleções além de arrays e objetos
1. Por que usar Map e Set? Limitações de objetos e arrays
1.1. Objetos comuns: chaves apenas string/Symbol, iteração limitada e herança de protótipo
Objetos JavaScript tradicionais possuem limitações significativas quando usados como dicionários ou mapas. As chaves são automaticamente convertidas para strings (ou Symbols), o que pode causar comportamentos inesperados:
const objeto = {};
objeto[1] = 'número';
objeto['1'] = 'string';
console.log(objeto); // { '1': 'string' } — a chave numérica sobrescreveu a string
// Herança de protótipo pode causar falsos positivos
const dados = {};
console.log(dados.toString); // [Function: toString] — herda do protótipo
console.log('toString' in dados); // true — mesmo sem ter definido
1.2. Arrays como conjuntos: busca ineficiente (O(n)) e duplicatas indesejadas
Arrays são ótimos para listas ordenadas, mas péssimos como conjuntos:
const ids = [1, 2, 3, 1, 4, 2];
// Verificar existência é O(n)
console.log(ids.includes(3)); // true — percorre todo o array
// Remover duplicatas exige código extra
const semDuplicatas = [...new Set(ids)]; // [1, 2, 3, 4]
1.3. Casos reais: cache, contagem de frequência, dados associativos complexos
// Cache com objeto — frágil com chaves não-string
const cache = {};
const usuario1 = { id: 1 };
cache[usuario1] = { dados: '...' }; // chave vira "[object Object]"
// Map resolve isso naturalmente
const cacheMap = new Map();
cacheMap.set(usuario1, { dados: '...' });
2. Map: o dicionário moderno
2.1. Sintaxe básica
const mapa = new Map();
mapa.set('nome', 'João');
mapa.set(42, 'resposta');
mapa.set({ id: 1 }, 'objeto como chave');
mapa.set(NaN, 'Not a Number');
console.log(mapa.get('nome')); // 'João'
console.log(mapa.has(42)); // true
console.log(mapa.size); // 4
mapa.delete('nome');
console.log(mapa.size); // 3
2.2. Chaves de qualquer tipo
Diferente de objetos, Map aceita qualquer valor como chave, mantendo a identidade do objeto:
const mapa = new Map();
const funcao = () => {};
const obj = {};
mapa.set(funcao, 'função como chave');
mapa.set(obj, 'objeto como chave');
mapa.set(document, 'DOM element');
console.log(mapa.get(funcao)); // 'função como chave'
console.log(mapa.get(obj)); // 'objeto como chave'
2.3. Iteração nativa
const usuarios = new Map([
[1, 'Alice'],
[2, 'Bob'],
[3, 'Charlie']
]);
// forEach
usuarios.forEach((valor, chave) => {
console.log(`${chave}: ${valor}`);
});
// for...of com entries(), keys(), values()
for (const [id, nome] of usuarios) {
console.log(`ID ${id}: ${nome}`);
}
console.log([...usuarios.keys()]); // [1, 2, 3]
console.log([...usuarios.values()]); // ['Alice', 'Bob', 'Charlie']
3. Set: conjuntos sem duplicatas
3.1. Criação e manipulação
const conjunto = new Set();
conjunto.add(1);
conjunto.add(2);
conjunto.add(1); // ignorado — já existe
conjunto.add('texto');
console.log(conjunto.has(1)); // true
console.log(conjunto.has(3)); // false
console.log(conjunto.size); // 3
conjunto.delete(2);
console.log(conjunto.size); // 2
3.2. Remoção automática de duplicatas
const comDuplicatas = [1, 2, 2, 3, 3, 3, 4];
const semDuplicatas = [...new Set(comDuplicatas)];
console.log(semDuplicatas); // [1, 2, 3, 4]
// Útil para IDs únicos de API
const respostas = await Promise.all([
fetch('/api/users/1'),
fetch('/api/users/2'),
fetch('/api/users/1') // requisição duplicada
]);
const idsUnicos = [...new Set(respostas.map(r => r.url))];
3.3. Operações de conjuntos
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// União
const uniao = new Set([...setA, ...setB]);
console.log([...uniao]); // [1, 2, 3, 4, 5, 6]
// Interseção
const intersecao = new Set([...setA].filter(x => setB.has(x)));
console.log([...intersecao]); // [3, 4]
// Diferença (A - B)
const diferenca = new Set([...setA].filter(x => !setB.has(x)));
console.log([...diferenca]); // [1, 2]
4. Performance e gerenciamento de memória
4.1. Comparação de desempenho
// Map vs Objeto para inserção/busca frequente
const tamanho = 100000;
// Objeto
const obj = {};
console.time('objeto-insercao');
for (let i = 0; i < tamanho; i++) obj[`key${i}`] = i;
console.timeEnd('objeto-insercao');
// Map
const map = new Map();
console.time('map-insercao');
for (let i = 0; i < tamanho; i++) map.set(`key${i}`, i);
console.timeEnd('map-insercao');
// Map geralmente é mais rápido para inserção/deleção frequente
4.2. WeakMap e WeakSet
WeakMap e WeakSet permitem que objetos sejam coletados pelo garbage collector quando não há mais referências:
const weakMap = new WeakMap();
let usuario = { id: 1 };
weakMap.set(usuario, { metadados: 'sensível' });
console.log(weakMap.has(usuario)); // true
usuario = null; // o objeto pode ser coletado
// weakMap.get(usuario) agora retorna undefined
4.3. Garbage collection com WeakMap em listeners
// Em Node.js ou React — evitar memory leaks
const listeners = new WeakMap();
function adicionarListener(elemento, callback) {
if (!listeners.has(elemento)) {
listeners.set(elemento, new Set());
}
listeners.get(elemento).add(callback);
elemento.addEventListener('click', callback);
}
// Quando elemento for removido do DOM e perder referências,
// o WeakMap permite que tudo seja coletado automaticamente
5. Map e Set no ecossistema Node.js
5.1. Cache de requisições HTTP com Map
const express = require('express');
const app = express();
const cache = new Map();
app.get('/api/users/:id', async (req, res) => {
const { id } = req.params;
if (cache.has(id)) {
console.log('Cache hit');
return res.json(cache.get(id));
}
const dados = await fetchUserFromDB(id);
cache.set(id, dados);
// Expiração simples após 5 minutos
setTimeout(() => cache.delete(id), 5 * 60 * 1000);
res.json(dados);
});
5.2. Controle de sessões e usuários únicos
const sessoesAtivas = new Set();
function login(usuarioId) {
if (sessoesAtivas.has(usuarioId)) {
throw new Error('Usuário já está logado');
}
sessoesAtivas.add(usuarioId);
}
function logout(usuarioId) {
sessoesAtivas.delete(usuarioId);
}
console.log(sessoesAtivas.size); // 0
login('user123');
console.log(sessoesAtivas.size); // 1
5.3. Serialização com JSON
const mapa = new Map([
['nome', 'João'],
['idade', 30],
['cidade', 'São Paulo']
]);
// Map não serializa diretamente com JSON.stringify
const serializado = JSON.stringify([...mapa]);
console.log(serializado);
// [["nome","João"],["idade",30],["cidade","São Paulo"]]
// Desserialização
const restaurado = new Map(JSON.parse(serializado));
console.log(restaurado.get('nome')); // 'João'
6. React: estado e dados reativos com Map e Set
6.1. Estado complexo com Map
import React, { useState } from 'react';
function ListaItens() {
const [itens, setItens] = useState(new Map());
const adicionarItem = (id, nome) => {
setItens(prev => new Map(prev).set(id, { nome, completo: false }));
};
const toggleItem = (id) => {
setItens(prev => {
const novo = new Map(prev);
const item = novo.get(id);
novo.set(id, { ...item, completo: !item.completo });
return novo;
});
};
return (
<ul>
{[...itens.entries()].map(([id, item]) => (
<li key={id} onClick={() => toggleItem(id)}>
{item.nome} {item.completo ? '✓' : ''}
</li>
))}
</ul>
);
}
6.2. Seleção múltipla com Set
function SelecaoCheckboxes({ opcoes }) {
const [selecionados, setSelecionados] = useState(new Set());
const toggle = (id) => {
setSelecionados(prev => {
const novo = new Set(prev);
if (novo.has(id)) {
novo.delete(id);
} else {
novo.add(id);
}
return novo;
});
};
return (
<div>
{opcoes.map(opcao => (
<label key={opcao.id}>
<input
type="checkbox"
checked={selecionados.has(opcao.id)}
onChange={() => toggle(opcao.id)}
/>
{opcao.nome}
</label>
))}
<p>Selecionados: {selecionados.size}</p>
</div>
);
}
6.3. Performance com cache
function ComponentePesado({ dados }) {
const [cache, setCache] = useState(new Map());
const processarDado = useCallback((id) => {
if (cache.has(id)) {
return cache.get(id);
}
const resultado = computacaoPesada(dados.find(d => d.id === id));
setCache(prev => new Map(prev).set(id, resultado));
return resultado;
}, [dados, cache]);
return (
<div>
{dados.map(dado => (
<Item key={dado.id} resultado={processarDado(dado.id)} />
))}
</div>
);
}
7. Padrões avançados e boas práticas
7.1. Map como dicionário polimórfico
const metadados = new Map();
function adicionarMetadados(objeto, chave, valor) {
if (!metadados.has(objeto)) {
metadados.set(objeto, new Map());
}
metadados.get(objeto).set(chave, valor);
}
const usuario = { nome: 'João' };
adicionarMetadados(usuario, 'criadoEm', Date.now());
adicionarMetadados(usuario, 'ultimoAcesso', Date.now());
7.2. Set para rastreamento de promises pendentes
const promisesPendentes = new Set();
async function requisicaoControlada(url) {
if (promisesPendentes.has(url)) {
throw new Error('Requisição já em andamento');
}
const promise = fetch(url);
promisesPendentes.add(url);
try {
return await promise;
} finally {
promisesPendentes.delete(url);
}
}
7.3. Map de Sets para relacionamentos muitos-para-muitos
const alunosPorCurso = new Map();
function matricularAluno(cursoId, alunoId) {
if (!alunosPorCurso.has(cursoId)) {
alunosPorCurso.set(cursoId, new Set());
}
alunosPorCurso.get(cursoId).add(alunoId);
}
function alunosDoCurso(cursoId) {
return alunosPorCurso.get(cursoId) || new Set();
}
matricularAluno('react-101', 'aluno1');
matricularAluno('react-101', 'aluno2');
matricularAluno('node-201', 'aluno1');
console.log([...alunosDoCurso('react-101')]); // ['aluno1', 'aluno2']
Referências
- MDN Web Docs: Map — Documentação oficial completa sobre Map, incluindo métodos, propriedades e exemplos de uso
- MDN Web Docs: Set — Documentação oficial sobre Set com exemplos de operações de conjuntos
- JavaScript.info: Map and Set — Tutorial detalhado com exemplos práticos e comparações entre Map, Set, objetos e arrays
- React Documentation: Managing State — Guia oficial do React sobre gerenciamento de estado, incluindo boas práticas com estruturas de dados
- Node.js Documentation: Garbage Collection — Documentação sobre gerenciamento de memória no Node.js, relevante para WeakMap e WeakSet
- Exploring JS: Maps and Sets — Livro online gratuito com explicações aprofundadas sobre Map e Set no ES6