Um Guia Prático para os Novos Métodos de Set do JavaScript
Métodos Set do JavaScript explicados: union, intersection, difference, symmetricDifference e testes de subconjunto, com suporte a Map e navegadores.
O objeto Set do JavaScript ganhou sete novos métodos de instância — union(), intersection(), difference(), symmetricDifference(), isSubsetOf(), isSupersetOf() e isDisjointFrom() — que se tornaram Baseline Newly Available em 11 de junho de 2024. Esses métodos substituem os padrões com Array.filter() + Array.includes() que desenvolvedores frontend implementavam manualmente há anos para comparar coleções, fazendo isso com lookups baseados em conjuntos em vez de varreduras aninhadas em arrays. Se você já conhece a API ES2015 do Set — add, has, delete, iteração — este guia cobre o que os posts de anúncio deixaram de fora: o protocolo de argumento Set-like que permite passar um Map, a armadilha de igualdade por referência que silenciosamente quebra comparações de objetos, padrões frontend prontos para uso, e uma tabela honesta de suporte a navegadores.
Principais Conclusões
- Todos os sete novos métodos de
Setsão Baseline Newly Available desde 11 de junho de 2024, disponíveis no Chrome 122, Edge 122, Firefox 127 e Safari 17, além do Node.js 22.0.0 — nenhum polyfill é necessário para os alvos atuais. - O argumento desses métodos não precisa ser uma instância de
Set; ele deve expor uma propriedadesizenumérica, um métodohaschamável e um métodokeyschamável, o que significa que umMapfunciona diretamente. - O
Setcompara valores por referência, portantonew Set([{id:1}]).intersection(new Set([{id:1}]))retorna um conjunto vazio — faça interseção de Sets de IDs primitivos estáveis. difference()não é comutativo, esymmetricDifference()retorna os elementos exclusivos do receptor primeiro, na ordem do receptor.- Substituir
[...a].filter(x => b.includes(x))pora.intersection(b)troca um padrão O(n²) com arrays por um que escala com o menor conjunto, dado o acesso médio sublinear aoSetexigido pela especificação.
Os sete métodos em resumo
Os novos métodos se dividem em dois grupos: quatro que retornam um novo Set e três que retornam um booleano. Essa divisão é o modelo mental mais útil — ela indica imediatamente se você está construindo uma coleção ou fazendo uma pergunta de sim/não.
Operações que retornam um novo Set:
union(other)— elementos neste conjunto ou no outro (pense emFULL OUTER JOINdo SQL).intersection(other)— elementos em ambos os conjuntos (pense emINNER JOIN).difference(other)— elementos neste conjunto mas não no outro (pense emLEFT JOIN).symmetricDifference(other)— elementos em qualquer um dos conjuntos, mas não em ambos.
Predicados que retornam um booleano:
isSubsetOf(other)—truese todo elemento deste conjunto estiver no outro.isSupersetOf(other)—truese este conjunto contiver todos os elementos do outro.isDisjointFrom(other)—truese os dois conjuntos não compartilharem nenhum elemento.
A proposta que adicionou esses métodos é a proposta de métodos de Set do TC39, que atingiu o Stage 4, está agora inativa, foi incorporada à especificação ECMAScript e faz parte do ECMAScript 2025.
Discover how at OpenReplay.com.
Usando os métodos que retornam conjuntos em código frontend real
Os quatro métodos que retornam conjuntos — union(), intersection(), difference() e symmetricDifference() — cada um retorna um novo Set sem modificar nenhuma das entradas, e cada um se encaixa perfeitamente em tarefas que desenvolvedores frontend já realizam manualmente.
union(): mesclar overrides de feature flags com os valores padrão
union() retorna um novo conjunto contendo todos os elementos de ambos os conjuntos, com duplicatas removidas.
const defaults = new Set(["new-dashboard", "dark-mode"]);
const overrides = new Set(["dark-mode", "beta-search"]);
const enabled = defaults.union(overrides);
// Set(3) { "new-dashboard", "dark-mode", "beta-search" }
A versão clássica disso era new Set([...defaults, ...overrides]). union() expressa a intenção diretamente. Ao mesclar um conjunto base de feature flags habilitadas com overrides por usuário ou por ambiente, union() fornece o conjunto efetivo de flags em uma única chamada.
intersection(): encontrar quais rotas um usuário pode realmente acessar
intersection() retorna um novo conjunto contendo apenas os elementos presentes em ambos os conjuntos.
const requiredForRoute = new Set<string>(["billing:read", "billing:write"]);
const userPermissions = new Set<string>(["billing:read", "users:read"]);
const satisfied = requiredForRoute.intersection(userPermissions);
// Set(1) { "billing:read" }
Para controle de acesso a rotas, faça a interseção do conjunto de permissões exigidas por uma rota com o conjunto de permissões que o usuário possui. O tamanho do resultado em relação ao conjunto requerido indica se o acesso é parcial ou completo.
difference(): comparar seleções anterior e atual em um componente multi-select
difference() retorna um novo conjunto contendo elementos deste conjunto que não estão no outro — e não é comutativo.
const prevSelected = new Set<string>(["a", "b", "c"]);
const nextSelected = new Set<string>(["b", "c", "d"]);
const added = nextSelected.difference(prevSelected); // Set(1) { "d" }
const removed = prevSelected.difference(nextSelected); // Set(1) { "a" }
Para componentes multi-select, nextSelected.difference(prevSelected) fornece os itens recém-adicionados, e prevSelected.difference(nextSelected) fornece os itens recém-removidos — duas operações de conjunto substituindo um padrão que exigia ordenação ou loops aninhados. a.difference(b) retorna os elementos de a ausentes em b, enquanto b.difference(a) retorna o inverso; a ordem dos argumentos define a operação.
symmetricDifference(): destacar quais chaves mudaram entre dois snapshots
symmetricDifference() retorna um novo conjunto contendo elementos em qualquer um dos conjuntos, mas não em ambos.
const before = new Set<string>(["name", "email", "phone"]);
const after = new Set<string>(["name", "email", "address"]);
const changedKeys = before.symmetricDifference(after);
// Set(2) { "phone", "address" }
Para destacar quais chaves apareceram ou desapareceram entre dois snapshots de um objeto de estado, calcule a diferença simétrica de seus conjuntos de chaves. Um detalhe que os posts de anúncio ignoram: a ordem de iteração depende do receptor. De acordo com o ECMA-262 2025, symmetricDifference() retorna os elementos exclusivos do receptor na ordem do receptor, seguidos pelos elementos exclusivos do outro na ordem de other.keys(). O conjunto de elementos é idêntico independentemente de qual lado você chama; a ordem não é.
Usando os predicados booleanos para verificações de autorização
Os três métodos predicado — isSubsetOf(), isSupersetOf() e isDisjointFrom() — cada um retorna um booleano, e cada um se encaixa em uma verificação comum de autorização ou validação de entrada.
isSupersetOf(): verificar se todos os escopos necessários estão presentes
isSupersetOf() retorna true se este conjunto contiver todos os elementos do conjunto fornecido.
const grantedScopes = new Set<string>(["read", "write", "delete"]);
const requiredScopes = new Set<string>(["read", "write"]);
const hasAllRequiredScopes = grantedScopes.isSupersetOf(requiredScopes);
// true
Para verificar se os escopos OAuth concedidos a um usuário cobrem todos os escopos necessários para uma operação, grantedScopes.isSupersetOf(requiredScopes) retorna true em uma única chamada — equivalente a [...requiredScopes].every(s => grantedScopes.has(s)), mas expresso como uma relação entre conjuntos.
isSubsetOf(): verificar se uma lista de tags é totalmente suportada
isSubsetOf() retorna true se todo elemento deste conjunto estiver no conjunto fornecido.
const supportedTags = new Set<string>(["sale", "new", "featured", "clearance"]);
const requestedTags = new Set<string>(["sale", "new"]);
const allSupported = requestedTags.isSubsetOf(supportedTags);
// true
Quando um chamador passa uma lista de tags ou filtros, requestedTags.isSubsetOf(supportedTags) confirma que todos são reconhecidos antes de você executar a consulta.
isDisjointFrom(): detectar teclas modificadoras conflitantes
isDisjointFrom() retorna true se os dois conjuntos não compartilharem nenhum elemento.
const pressedKeys = new Set<string>(["Meta", "Shift"]);
const conflictingModifiers = new Set<string>(["Control", "Alt"]);
const noConflict = pressedKeys.isDisjointFrom(conflictingModifiers);
// true
Para o tratamento de atalhos de teclado, isDisjointFrom() permite verificar que nenhuma tecla modificadora conflitante está pressionada antes de disparar uma ação.
O protocolo Set-like: o argumento não precisa ser um Set
O argumento de qualquer um desses métodos não precisa ser uma instância de Set — ele deve ser um objeto com uma propriedade size numérica, um método has chamável e um método keys chamável. Um Map satisfaz esse requisito nativamente, o que significa que mySet.intersection(myMap) é válido e verifica as chaves do mapa. Todos os posts de anúncio descrevem o argumento como “outro conjunto”, o que é tecnicamente incompleto.
Esse protocolo é definido na especificação ECMA-262 em GetSetRecord, e o MDN documenta o requisito de objeto Set-like diretamente.
const map = new Map([
["a", 1],
["b", 2],
]);
const set = new Set(["a", "c"]);
set.intersection(map);
// Set(1) { "a" } — verifica contra as chaves do mapa
Confirmado no Node v22.16.0, set.intersection(map) retorna Set { 'a' } porque "a" é o único elemento do conjunto que também aparece entre as chaves do mapa. Isso é relevante para interoperabilidade: você pode fazer a interseção de um Set com as chaves de um Map sem construir um new Set(map.keys()) intermediário, e qualquer biblioteca de coleções imutáveis ou objeto de índice personalizado que exponha size, has e keys pode ser usado diretamente nesses métodos sem conversão.
A armadilha da identidade de objetos
O Set compara valores por referência, não por estrutura. A documentação de igualdade de valores do MDN confirma isso. A consequência prática é uma armadilha quando seu conjunto contém objetos:
new Set([{ id: 1 }]).intersection(new Set([{ id: 1 }]));
// Set(0) {}
A igualdade por referência é a armadilha: isso retorna um conjunto vazio porque os dois objetos {id: 1} são referências distintas, mesmo que pareçam idênticos. A solução é fazer a interseção de Sets de IDs primitivos estáveis e depois reidratar a partir de um mapa de lookup:
type User = { id: number; name: string };
const prev: User[] = [{ id: 1, name: "Ada" }, { id: 2, name: "Lin" }];
const next: User[] = [{ id: 2, name: "Lin" }, { id: 3, name: "Mo" }];
const byId = new Map(next.map((u) => [u.id, u]));
const prevIds = new Set(prev.map((u) => u.id));
const nextIds = new Set(next.map((u) => u.id));
const addedIds = nextIds.difference(prevIds); // Set(1) { 3 }
const added = [...addedIds].map((id) => byId.get(id)!);
// [{ id: 3, name: "Mo" }]
Bugs de igualdade por referência como esse não deixam stack trace. A operação de conjunto é concluída sem erro, retorna um conjunto vazio e a interface simplesmente não é atualizada — um multi-select que não muda, um badge de permissão que não some, uma visualização de diff que não mostra alterações. Como não há exceção, o problema não aparece no monitoramento de erros. Session replay é uma técnica que torna visível essa classe de bugs de falha silenciosa: você observa o usuário interagir, a operação ser concluída e a interface não responder a nada.
Performance: por que isso supera filter + includes
Substituir [...a].filter(x => b.includes(x)) por a.intersection(b) não é apenas um ganho de legibilidade. Array.includes() é O(n), tornando o padrão com filter O(n²) sobre duas coleções de n elementos, enquanto intersection() escala com o menor dos dois conjuntos, assumindo o acesso médio sublinear ao Set que a especificação exige. A seção de objetos Set do ECMA-262 determina que o acesso ao Set deve ser sublinear em média — não garante O(1) — e a referência do MDN para intersection() descreve o comportamento de escala com o menor conjunto.
| Operação | Padrão com Array | Complexidade | Método nativo de Set | Complexidade |
|---|---|---|---|---|
| Interseção | [...a].filter(x => b.includes(x)) | O(n²) | a.intersection(b) | escala com o menor conjunto, acesso médio sublinear |
| União | new Set([...a, ...b]) | O(n + m) | a.union(b) | O(n + m) |
| Diferença | [...a].filter(x => !b.includes(x)) | O(n²) | a.difference(b) | escala com a, acesso médio sublinear |
A diferença aumenta com o tamanho da coleção: com poucos elementos é irrelevante, mas os padrões com arrays degradam quadraticamente enquanto os métodos nativos não.
Receitas de migração
Mapeie seu código existente de comparação de arrays para os novos métodos diretamente. Essas três substituições cobrem a maioria dos casos do mundo real.
| Padrão antigo | Novo método |
|---|---|
[...a].filter(x => b.includes(x)) | a.intersection(b) |
[...new Set([...a, ...b])] | a.union(b) (retorna um Set) |
[...a].filter(x => !b.includes(x)) | a.difference(b) |
Se seus dados estão em arrays, envolva cada lado em new Set(...) uma vez, execute a operação e faça o spread de volta para um array se uma API downstream precisar de um:
const a = ["x", "y", "z"];
const b = ["y", "z", "w"];
const common = [...new Set(a).intersection(new Set(b))];
// ["y", "z"]
A conversão para Set é em si O(n), mas a ida e volta evita a varredura aninhada exigida pelo padrão O(n²) com filter + includes e escala melhor à medida que as coleções crescem.
Suporte a navegadores e polyfills
Todos os sete métodos de Set são Baseline Newly Available desde 11 de junho de 2024, disponíveis no Chrome 122 (20 de fev. de 2024), Edge 122 (23 de fev. de 2024), Firefox 127 (11 de jun. de 2024) e Safari 17 (18 de set. de 2023), sem necessidade de polyfill para os alvos atuais de navegadores. Eles também estão disponíveis no Node.js 22.0.0 (24 de abr. de 2024) via V8 12.4.
| Ambiente | Versão | Data de lançamento |
|---|---|---|
| Chrome | 122 | 20 de fev. de 2024 |
| Edge | 122 | 23 de fev. de 2024 |
| Firefox | 127 | 11 de jun. de 2024 |
| Safari | 17 | 18 de set. de 2023 |
| Node.js | 22.0.0 (V8 12.4) | 24 de abr. de 2024 |
Para alvos mais antigos, a biblioteca core-js e o projeto es-shims fornecem polyfills compatíveis com a especificação. Se você suporta apenas navegadores evergreen atuais e Node.js 22+, pode dispensar o polyfill completamente.
Próximos passos
Audite sua base de código em busca de filter + includes e filter + !includes sobre dados deduplicados — esses são os candidatos diretos para intersection() e difference(). Antes de migrar coleções que contêm objetos, converta-as para Sets de IDs primitivos estáveis para evitar a armadilha de igualdade por referência, e lembre-se de que qualquer Map, coleção imutável ou índice personalizado que exponha size, has e keys pode ser passado diretamente como argumento. Os métodos são Baseline, a semântica é estável na especificação, e os padrões com arrays que eles substituem nunca foram tão eficientes quanto pareciam.
Perguntas Frequentes
Posso passar um Map diretamente para Set.intersection() ou preciso convertê-lo primeiro?
Você pode passar um Map diretamente. Os novos métodos de Set aceitam qualquer objeto Set-like, definido na especificação ECMA-262 como um objeto com uma propriedade size numérica, um método has chamável e um método keys chamável. Um Map satisfaz isso nativamente, portanto mySet.intersection(myMap) verifica as chaves do mapa sem construir um new Set(map.keys()) intermediário. Sets de bibliotecas de coleções imutáveis e objetos de índice personalizados que expõem os mesmos três membros também funcionam sem conversão.
Por que a interseção de Sets retorna um conjunto vazio quando ambos contêm objetos idênticos?
Porque o Set compara valores por referência, não por estrutura. A expressão new Set([{id:1}]).intersection(new Set([{id:1}])) retorna um conjunto vazio, pois os dois objetos {id:1} são referências distintas mesmo que pareçam idênticos, conforme confirmado no Node v22.16.0. A solução é construir Sets de IDs primitivos estáveis, executar a operação sobre eles e depois reidratar os objetos correspondentes a partir de um Map de lookup indexado por ID.
Qual é a diferença entre difference() e symmetricDifference()?
difference() é direcional e não comutativo: a.difference(b) retorna os elementos de a ausentes em b, enquanto b.difference(a) retorna o inverso. symmetricDifference() retorna os elementos em qualquer um dos conjuntos, mas não em ambos, e é independente de ordem em termos de conteúdo. As ordens de iteração também diferem: symmetricDifference() retorna os elementos exclusivos do receptor na ordem do receptor, seguidos pelos elementos exclusivos do outro na ordem de other.keys(), portanto o conjunto de resultados é idêntico independentemente de qual lado você chama, mas a ordenação não é.
Ainda preciso de um polyfill para os novos métodos de Set em produção?
Não para os alvos atuais de navegadores. Todos os sete métodos são Baseline Newly Available desde 11 de junho de 2024, disponíveis no Chrome 122, Edge 122, Firefox 127, Safari 17 e Node.js 22.0.0 via V8 12.4. Se você suporta apenas navegadores evergreen atuais e Node.js 22 ou posterior, pode dispensar o polyfill completamente. Para alvos mais antigos, a biblioteca core-js e o projeto es-shims fornecem polyfills compatíveis com a especificação que você pode incluir seletivamente.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Star on GitHub12k