12k
All articles

Guía Práctica de los Nuevos Métodos de Set en JavaScript

Métodos Set de JavaScript explicados: union, intersección, diferencia, symmetricDifference y pruebas de subconjunto, con soporte Map y navegadores.

OpenReplay Team
OpenReplay Team
Guía Práctica de los Nuevos Métodos de Set en JavaScript

El objeto Set de JavaScript incorporó siete nuevos métodos de instancia — union(), intersection(), difference(), symmetricDifference(), isSubsetOf(), isSupersetOf() e isDisjointFrom() — que pasaron a formar parte de Baseline Newly Available a partir del 11 de junio de 2024. Estos métodos reemplazan los patrones basados en Array.filter() + Array.includes() que los desarrolladores frontend han implementado manualmente durante años para comparar colecciones, y lo hacen mediante búsquedas propias de conjuntos en lugar de recorridos anidados sobre arrays. Si ya conoces la API de Set de ES2015 — add, has, delete, iteración — esta guía te ofrece lo que los artículos de anuncio omitieron: el protocolo de argumento Set-like que permite pasar un Map, la trampa de igualdad por referencia que rompe silenciosamente las comparaciones de objetos, patrones frontend listos para copiar y pegar, y una tabla de compatibilidad de navegadores sin rodeos.

Puntos Clave

  • Los siete nuevos métodos de Set son Baseline Newly Available desde el 11 de junio de 2024, disponibles en Chrome 122, Edge 122, Firefox 127 y Safari 17, además de Node.js 22.0.0 — no se requiere ningún polyfill para los entornos objetivo actuales.
  • El argumento de estos métodos no necesita ser una instancia de Set; debe exponer una propiedad numérica size, un método has invocable y un método keys invocable, lo que significa que un Map funciona directamente.
  • Set compara valores por referencia, por lo que new Set([{id:1}]).intersection(new Set([{id:1}])) devuelve un conjunto vacío — en su lugar, intersecta Sets de IDs primitivos estables.
  • difference() no es conmutativo, y symmetricDifference() devuelve primero los elementos exclusivos del receptor, en el orden del receptor.
  • Reemplazar [...a].filter(x => b.includes(x)) por a.intersection(b) sustituye un patrón de array O(n²) por uno que escala con el conjunto más pequeño, según el acceso promedio sublineal a Set que exige la especificación.

Los siete métodos de un vistazo

Los nuevos métodos se dividen en dos grupos: cuatro que devuelven un nuevo Set y tres que devuelven un booleano. Esta división es el modelo mental más útil — te indica de inmediato si estás construyendo una colección o formulando una pregunta de sí o no.

Operaciones que devuelven un nuevo Set:

  • union(other) — elementos en este conjunto o en el otro (equivalente a FULL OUTER JOIN en SQL).
  • intersection(other) — elementos presentes en ambos conjuntos (equivalente a INNER JOIN).
  • difference(other) — elementos en este conjunto pero no en el otro (equivalente a LEFT JOIN).
  • symmetricDifference(other) — elementos en cualquiera de los dos conjuntos, pero no en ambos.

Predicados que devuelven un booleano:

La propuesta que incorporó estos métodos es la propuesta de métodos de Set del TC39, que alcanzó el Stage 4, está ahora inactiva, fue incorporada a la especificación ECMAScript y forma parte de ECMAScript 2025.

Uso de los métodos que devuelven conjuntos en código frontend real

Los cuatro métodos que devuelven conjuntos — union(), intersection(), difference() y symmetricDifference() — devuelven cada uno un nuevo Set sin mutar ninguna de las entradas, y cada uno se corresponde directamente con una tarea que los desarrolladores frontend ya realizan manualmente.

union(): combinar overrides de feature flags con los valores por defecto

union() devuelve un nuevo conjunto con todos los elementos de ambos conjuntos, eliminando duplicados.

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

La versión clásica de esto era new Set([...defaults, ...overrides]). union() expresa la intención directamente. Cuando combinas un conjunto base de feature flags habilitados con overrides por usuario o por entorno, union() te proporciona el conjunto efectivo de flags en una sola llamada.

intersection(): encontrar las rutas a las que un usuario puede acceder realmente

intersection() devuelve un nuevo conjunto con solo los elementos presentes en ambos 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 el control de acceso a rutas, intersecta el conjunto de permisos que requiere una ruta con el conjunto de permisos que posee el usuario. El tamaño del resultado en relación con el conjunto requerido indica si el acceso es parcial o completo.

difference(): comparar la selección anterior con la nueva en un componente multi-select

difference() devuelve un nuevo conjunto con los elementos de este conjunto que no están en el otro — y no es conmutativo.

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

En componentes multi-select, nextSelected.difference(prevSelected) te da los elementos recién añadidos, y prevSelected.difference(nextSelected) los recién eliminados — dos operaciones de conjunto que reemplazan un patrón que requería ordenación o bucles anidados. a.difference(b) devuelve los elementos de a ausentes en b, mientras que b.difference(a) devuelve lo inverso; el orden de los argumentos define la operación.

symmetricDifference(): resaltar qué claves cambiaron entre dos snapshots

symmetricDifference() devuelve un nuevo conjunto con los elementos que están en cualquiera de los dos conjuntos, pero no en 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 resaltar qué claves aparecieron o desaparecieron entre dos snapshots de un objeto de estado, calcula la diferencia simétrica de sus conjuntos de claves. Un detalle que los artículos de anuncio pasan por alto: el orden de iteración depende del receptor. Según ECMA-262 2025, symmetricDifference() devuelve primero los elementos exclusivos del receptor en el orden del receptor, seguidos de los elementos exclusivos del otro en el orden de other.keys(). El conjunto de elementos es idéntico independientemente del lado desde el que se llame; el orden no lo es.

Uso de los predicados booleanos para verificaciones de autorización

Los tres métodos predicado — isSubsetOf(), isSupersetOf() e isDisjointFrom() — devuelven cada uno un booleano, y cada uno se corresponde con una verificación habitual de autorización o validación de entrada.

isSupersetOf(): verificar que todos los scopes requeridos están presentes

isSupersetOf() devuelve true si este conjunto contiene cada elemento del conjunto dado.

const grantedScopes = new Set<string>(["read", "write", "delete"]);
const requiredScopes = new Set<string>(["read", "write"]);

const hasAllRequiredScopes = grantedScopes.isSupersetOf(requiredScopes);
// true

Para comprobar si los scopes OAuth concedidos a un usuario cubren todos los scopes requeridos para una operación, grantedScopes.isSupersetOf(requiredScopes) devuelve true en una sola llamada — equivalente a [...requiredScopes].every(s => grantedScopes.has(s)), pero expresado como una relación de conjuntos.

isSubsetOf(): verificar que una lista de etiquetas está completamente soportada

isSubsetOf() devuelve true si cada elemento de este conjunto está en el conjunto dado.

const supportedTags = new Set<string>(["sale", "new", "featured", "clearance"]);
const requestedTags = new Set<string>(["sale", "new"]);

const allSupported = requestedTags.isSubsetOf(supportedTags);
// true

Cuando un cliente pasa una lista de etiquetas o filtros, requestedTags.isSubsetOf(supportedTags) confirma que todos son reconocidos antes de ejecutar la consulta.

isDisjointFrom(): detectar teclas modificadoras en conflicto

isDisjointFrom() devuelve true si los dos conjuntos no comparten ningún elemento.

const pressedKeys = new Set<string>(["Meta", "Shift"]);
const conflictingModifiers = new Set<string>(["Control", "Alt"]);

const noConflict = pressedKeys.isDisjointFrom(conflictingModifiers);
// true

Para el manejo de atajos de teclado, isDisjointFrom() permite verificar que no hay teclas modificadoras en conflicto presionadas antes de ejecutar una acción.

El protocolo Set-like: el argumento no tiene que ser un Set

El argumento de cualquiera de estos métodos no necesita ser una instancia de Set — debe ser un objeto con una propiedad numérica size, un método has invocable y un método keys invocable. Un Map cumple este requisito de forma nativa, lo que significa que mySet.intersection(myMap) es válido y verifica contra las claves del mapa. Todos los artículos de anuncio describen el argumento como “otro conjunto”, lo cual es técnicamente incompleto.

Este protocolo está definido en la especificación ECMA-262 bajo GetSetRecord, y MDN documenta directamente el requisito de objeto Set-like.

const map = new Map([
  ["a", 1],
  ["b", 2],
]);
const set = new Set(["a", "c"]);

set.intersection(map);
// Set(1) { "a" }  — verifica contra las claves del mapa

Confirmado en Node v22.16.0, set.intersection(map) devuelve Set { 'a' } porque "a" es el único elemento del conjunto que también aparece entre las claves del mapa. Esto es relevante para la interoperabilidad: puedes intersectar un Set con las claves de un Map sin construir un new Set(map.keys()) intermedio, y cualquier librería de colecciones inmutables u objeto de índice personalizado que exponga size, has y keys puede conectarse a estos métodos sin conversión.

La trampa de la identidad de objetos

Set compara valores por referencia, no por estructura. La documentación de igualdad de valores de MDN lo confirma. La consecuencia práctica es una trampa cuando tu conjunto contiene objetos:

new Set([{ id: 1 }]).intersection(new Set([{ id: 1 }]));
// Set(0) {}

La igualdad por referencia es la trampa: esto devuelve un conjunto vacío porque los dos objetos {id: 1} son referencias distintas, aunque parezcan idénticos. La solución es intersectar Sets de IDs primitivos estables y luego rehidratar los objetos desde un mapa de búsqueda:

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" }]

Los errores de igualdad por referencia como este no dejan ningún stack trace. La operación de conjunto se completa sin error, devuelve un conjunto vacío y la interfaz simplemente no se actualiza — un multi-select que no cambia, un badge de permisos que no se limpia, una vista de diff que no muestra cambios. Como no se lanza ninguna excepción, no aparece en los sistemas de monitoreo de errores. La reproducción de sesiones es una técnica que hace visible esta clase de fallos silenciosos: puedes observar al usuario interactuar, la operación completarse y la interfaz no responder a nada.

Rendimiento: por qué supera al patrón filter + includes

Reemplazar [...a].filter(x => b.includes(x)) por a.intersection(b) no es solo una mejora de legibilidad. Array.includes() es O(n), lo que hace que el patrón con filter sea O(n²) sobre dos colecciones de n elementos, mientras que intersection() escala con el menor de los dos conjuntos, asumiendo el acceso promedio sublineal a Set que exige la especificación. La sección de objetos Set de ECMA-262 exige que el acceso a Set sea sublineal en promedio — no garantiza O(1) — y la referencia de intersection() en MDN describe el comportamiento de escalado según el conjunto más pequeño.

OperaciónPatrón con ArrayComplejidadMétodo nativo de SetComplejidad
Intersección[...a].filter(x => b.includes(x))O(n²)a.intersection(b)escala con el conjunto más pequeño, acceso promedio sublineal
Uniónnew Set([...a, ...b])O(n + m)a.union(b)O(n + m)
Diferencia[...a].filter(x => !b.includes(x))O(n²)a.difference(b)escala con a, acceso promedio sublineal

La brecha se amplía con el tamaño de la colección: con pocos elementos es irrelevante, pero los patrones con arrays degradan cuadráticamente mientras que los métodos nativos no lo hacen.

Recetas de migración

Mapea tu código existente de comparación de arrays directamente a los nuevos métodos. Estas tres sustituciones cubren la mayoría de los casos del mundo real.

Patrón anteriorNuevo método
[...a].filter(x => b.includes(x))a.intersection(b)
[...new Set([...a, ...b])]a.union(b) (devuelve un Set)
[...a].filter(x => !b.includes(x))a.difference(b)

Si tus datos están en arrays, envuelve cada lado en new Set(...) una vez, ejecuta la operación y vuelve a convertir a array con spread si una API downstream lo requiere:

const a = ["x", "y", "z"];
const b = ["y", "z", "w"];

const common = [...new Set(a).intersection(new Set(b))];
// ["y", "z"]

La conversión a Set es en sí misma O(n), pero el viaje de ida y vuelta evita el recorrido anidado que requiere el patrón O(n²) de filter + includes y escala mejor a medida que crecen las colecciones.

Compatibilidad con navegadores y polyfills

Los siete métodos de Set son Baseline Newly Available desde el 11 de junio de 2024, disponibles en Chrome 122 (20 feb. 2024), Edge 122 (23 feb. 2024), Firefox 127 (11 jun. 2024) y Safari 17 (18 sep. 2023), sin necesidad de polyfill para los entornos objetivo actuales. También están disponibles en Node.js 22.0.0 (24 abr. 2024) a través de V8 12.4.

EntornoVersiónFecha de lanzamiento
Chrome12220 feb. 2024
Edge12223 feb. 2024
Firefox12711 jun. 2024
Safari1718 sep. 2023
Node.js22.0.0 (V8 12.4)24 abr. 2024

Para entornos más antiguos, la librería core-js y el proyecto es-shims proporcionan polyfills conformes a la especificación. Si solo soportas navegadores evergreen actuales y Node.js 22+, puedes prescindir del polyfill por completo.

Próximos pasos

Revisa tu base de código en busca de patrones filter + includes y filter + !includes sobre datos deduplicados — esos son los candidatos directos para intersection() y difference(). Antes de migrar colecciones que contienen objetos, conviértelas a Sets de IDs primitivos estables para evitar la trampa de la igualdad por referencia, y recuerda que cualquier Map, colección inmutable u índice personalizado que exponga size, has y keys puede pasarse directamente como argumento. Los métodos son Baseline, la semántica es estable en la especificación, y los patrones con arrays que reemplazan nunca fueron tan eficientes como parecían.

Preguntas Frecuentes

¿Puedo pasar un Map directamente a Set.intersection() o necesito convertirlo primero?

Puedes pasar un Map directamente. Los nuevos métodos de Set aceptan cualquier objeto Set-like, definido en la especificación ECMA-262 como un objeto con una propiedad numérica size, un método has invocable y un método keys invocable. Un Map lo cumple de forma nativa, por lo que mySet.intersection(myMap) verifica contra las claves del mapa sin necesidad de construir un new Set(map.keys()) intermedio. Los conjuntos de librerías de colecciones inmutables y los objetos de índice personalizados que expongan los mismos tres miembros también funcionan sin conversión.

¿Por qué la intersección de Sets devuelve un conjunto vacío cuando ambos conjuntos contienen objetos idénticos?

Porque Set compara valores por referencia, no por estructura. La expresión new Set([{id:1}]).intersection(new Set([{id:1}])) devuelve un conjunto vacío ya que los dos objetos {id:1} son referencias distintas aunque parezcan idénticos, confirmado en Node v22.16.0. La solución es construir Sets de IDs primitivos estables, ejecutar la operación sobre ellos y luego rehidratar los objetos coincidentes desde un Map de búsqueda indexado por ID.

¿Cuál es la diferencia entre difference() y symmetricDifference()?

difference() es direccional y no conmutativo: a.difference(b) devuelve los elementos de a que están ausentes en b, mientras que b.difference(a) devuelve lo inverso. symmetricDifference() devuelve los elementos que están en cualquiera de los dos conjuntos pero no en ambos, y es independiente del orden en cuanto al contenido. Sus órdenes de iteración también difieren: symmetricDifference() devuelve primero los elementos exclusivos del receptor en el orden del receptor, seguidos de los elementos exclusivos del otro en el orden de other.keys(), por lo que el conjunto de resultados es idéntico independientemente del lado desde el que se llame, pero el orden no lo es.

¿Sigo necesitando un polyfill para los nuevos métodos de Set en producción?

No para los entornos objetivo actuales. Los siete métodos son Baseline Newly Available desde el 11 de junio de 2024, disponibles en Chrome 122, Edge 122, Firefox 127, Safari 17 y Node.js 22.0.0 a través de V8 12.4. Si solo soportas navegadores evergreen actuales y Node.js 22 o posterior, puedes prescindir del polyfill por completo. Para entornos más antiguos, la librería core-js y el proyecto es-shims proporcionan polyfills conformes a la especificación que puedes incluir de forma selectiva.

Open-source session replay

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

We use cookies to improve your experience. By using our site, you accept cookies.