Guide pratique des nouvelles méthodes Set de JavaScript
Méthodes Set JavaScript expliquées: union, intersection, différence, symmetricDifference et tests de sous-ensemble, avec support Map et navigateurs.
L’objet Set de JavaScript a bénéficié de sept nouvelles méthodes d’instance — union(), intersection(), difference(), symmetricDifference(), isSubsetOf(), isSupersetOf() et isDisjointFrom() — qui sont devenues Baseline Newly Available le 11 juin 2024. Ces méthodes remplacent les combinaisons Array.filter() + Array.includes() que les développeurs frontend ont codées manuellement pendant des années pour comparer des collections, en s’appuyant sur des recherches basées sur les ensembles plutôt que sur des parcours imbriqués de tableaux. Si vous maîtrisez déjà l’API ES2015 Set — add, has, delete, l’itération — ce guide vous apporte ce que les articles d’annonce ont omis : le protocole d’argument Set-like qui vous permet de passer une Map, le piège de l’égalité par référence qui brise silencieusement les comparaisons d’objets, des patterns frontend prêts à l’emploi, et un tableau de compatibilité navigateur honnête.
Points clés
- Les sept nouvelles méthodes
Setsont Baseline Newly Available depuis le 11 juin 2024, disponibles dans Chrome 122, Edge 122, Firefox 127 et Safari 17, ainsi que dans Node.js 22.0.0 — aucun polyfill n’est nécessaire pour les cibles actuelles. - L’argument passé à ces méthodes n’a pas besoin d’être une instance de
Set; il doit exposer une propriétésizenumérique, une méthodehasappelable et une méthodekeysappelable, ce qui signifie qu’uneMapfonctionne directement. Setcompare les valeurs par référence, doncnew Set([{id:1}]).intersection(new Set([{id:1}]))retourne un ensemble vide — effectuez plutôt des intersections sur des ensembles d’identifiants primitifs stables.difference()n’est pas commutative, etsymmetricDifference()retourne d’abord les éléments propres au récepteur, dans l’ordre du récepteur.- Remplacer
[...a].filter(x => b.includes(x))para.intersection(b)substitue un pattern O(n²) sur des tableaux par une opération qui passe à l’échelle avec le plus petit ensemble, grâce à l’accès moyen sous-linéaire àSetexigé par la spécification.
Les sept méthodes en un coup d’œil
Les nouvelles méthodes se répartissent en deux groupes : quatre qui retournent un nouveau Set et trois qui retournent un booléen. Cette distinction est le modèle mental le plus utile — elle vous indique immédiatement si vous construisez une collection ou si vous posez une question fermée.
Opérations qui retournent un nouveau Set :
union(other)— les éléments présents dans cet ensemble ou dans l’autre (pensezFULL OUTER JOINen SQL).intersection(other)— les éléments présents dans les deux ensembles (pensezINNER JOIN).difference(other)— les éléments présents dans cet ensemble mais pas dans l’autre (pensezLEFT JOIN).symmetricDifference(other)— les éléments présents dans l’un ou l’autre ensemble, mais pas dans les deux.
Prédicats qui retournent un booléen :
isSubsetOf(other)—truesi chaque élément de cet ensemble est présent dans l’autre.isSupersetOf(other)—truesi cet ensemble contient chaque élément de l’autre.isDisjointFrom(other)—truesi les deux ensembles ne partagent aucun élément.
La proposition qui a introduit ces méthodes est la proposition TC39 sur les méthodes Set, qui a atteint le Stage 4, est désormais inactive, a été intégrée à la spécification ECMAScript et fait partie d’ECMAScript 2025.
Discover how at OpenReplay.com.
Utiliser les méthodes retournant un Set dans du code frontend réel
Les quatre méthodes retournant un ensemble — union(), intersection(), difference() et symmetricDifference() — retournent chacune un nouveau Set sans muter aucune des entrées, et chacune correspond directement à une tâche que les développeurs frontend effectuent déjà manuellement.
union() : fusionner des overrides de feature flags avec les valeurs par défaut
union() retourne un nouvel ensemble contenant tous les éléments des deux ensembles, sans doublons.
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 version classique de ce pattern était new Set([...defaults, ...overrides]). union() exprime l’intention directement. Lorsque vous fusionnez un ensemble de base de feature flags activés avec des overrides par utilisateur ou par environnement, union() vous donne l’ensemble effectif des flags en un seul appel.
intersection() : identifier les routes auxquelles un utilisateur peut réellement accéder
intersection() retourne un nouvel ensemble contenant uniquement les éléments présents dans les deux ensembles.
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" }
Pour le contrôle d’accès aux routes, effectuez l’intersection entre l’ensemble des permissions requises par une route et l’ensemble des permissions détenues par l’utilisateur. La taille du résultat par rapport à l’ensemble requis vous indique si l’accès est partiel ou complet.
difference() : calculer le diff entre une sélection précédente et une nouvelle dans un composant multi-sélection
difference() retourne un nouvel ensemble contenant les éléments présents dans cet ensemble mais pas dans l’autre — et elle n’est pas commutative.
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" }
Pour les composants multi-sélection, nextSelected.difference(prevSelected) vous donne les éléments qui viennent d’être ajoutés, et prevSelected.difference(nextSelected) vous donne ceux qui viennent d’être supprimés — deux opérations sur des ensembles remplaçant un pattern qui nécessitait du tri ou des boucles imbriquées. a.difference(b) retourne les éléments de a absents de b, tandis que b.difference(a) retourne l’inverse ; l’ordre des arguments définit l’opération.
symmetricDifference() : mettre en évidence les clés modifiées entre deux snapshots
symmetricDifference() retourne un nouvel ensemble contenant les éléments présents dans l’un ou l’autre ensemble, mais pas dans les deux.
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" }
Pour mettre en évidence les clés apparues ou disparues entre deux snapshots d’un objet d’état, calculez la différence symétrique de leurs ensembles de clés. Un détail que les articles d’annonce passent sous silence : l’ordre d’itération dépend du récepteur. Selon ECMA-262 2025, symmetricDifference() retourne d’abord les éléments propres au récepteur dans l’ordre du récepteur, suivis des éléments propres à l’autre ensemble dans l’ordre de other.keys(). L’ensemble des éléments est identique quel que soit le côté depuis lequel vous l’appelez ; l’ordre, lui, ne l’est pas.
Utiliser les prédicats booléens pour les vérifications d’autorisation
Les trois méthodes prédicats — isSubsetOf(), isSupersetOf() et isDisjointFrom() — retournent chacune un booléen et correspondent chacune à une vérification d’autorisation ou de validation d’entrée courante.
isSupersetOf() : vérifier la présence de tous les scopes requis
isSupersetOf() retourne true si cet ensemble contient chaque élément de l’ensemble donné.
const grantedScopes = new Set<string>(["read", "write", "delete"]);
const requiredScopes = new Set<string>(["read", "write"]);
const hasAllRequiredScopes = grantedScopes.isSupersetOf(requiredScopes);
// true
Pour vérifier si les scopes OAuth accordés à un utilisateur couvrent tous les scopes requis pour une opération, grantedScopes.isSupersetOf(requiredScopes) retourne true en un seul appel — équivalent à [...requiredScopes].every(s => grantedScopes.has(s)), mais exprimé comme une relation ensembliste.
isSubsetOf() : vérifier qu’une liste de tags est entièrement prise en charge
isSubsetOf() retourne true si chaque élément de cet ensemble est présent dans l’ensemble donné.
const supportedTags = new Set<string>(["sale", "new", "featured", "clearance"]);
const requestedTags = new Set<string>(["sale", "new"]);
const allSupported = requestedTags.isSubsetOf(supportedTags);
// true
Lorsqu’un appelant transmet une liste de tags ou de filtres, requestedTags.isSubsetOf(supportedTags) confirme que chacun est reconnu avant d’exécuter la requête.
isDisjointFrom() : détecter les touches de modification conflictuelles
isDisjointFrom() retourne true si les deux ensembles ne partagent aucun élément.
const pressedKeys = new Set<string>(["Meta", "Shift"]);
const conflictingModifiers = new Set<string>(["Control", "Alt"]);
const noConflict = pressedKeys.isDisjointFrom(conflictingModifiers);
// true
Pour la gestion des raccourcis clavier, isDisjointFrom() vous permet de vérifier qu’aucune touche de modification conflictuelle n’est enfoncée avant de déclencher une action.
Le protocole Set-like : l’argument n’est pas nécessairement un Set
L’argument passé à l’une de ces méthodes n’a pas besoin d’être une instance de Set — il doit être un objet disposant d’une propriété size numérique, d’une méthode has appelable et d’une méthode keys appelable. Une Map satisfait nativement à cette exigence, ce qui signifie que mySet.intersection(myMap) est valide et effectue la vérification sur les clés de la map. Chaque article d’annonce décrit l’argument comme « un autre ensemble », ce qui est techniquement incomplet.
Ce protocole est défini dans la spécification ECMA-262 sous GetSetRecord, et MDN documente directement l’exigence d’objet Set-like.
const map = new Map([
["a", 1],
["b", 2],
]);
const set = new Set(["a", "c"]);
set.intersection(map);
// Set(1) { "a" } — vérifie par rapport aux clés de la map
Confirmé dans Node v22.16.0, set.intersection(map) retourne Set { 'a' } car "a" est le seul élément de l’ensemble qui apparaît également parmi les clés de la map. Cela est important pour l’interopérabilité : vous pouvez effectuer l’intersection d’un Set avec les clés d’une Map sans construire un new Set(map.keys()) intermédiaire, et toute bibliothèque de collections immuables ou tout objet d’index personnalisé exposant size, has et keys s’intègre à ces méthodes sans conversion.
Le piège de l’identité des objets
Set compare les valeurs par référence, et non par structure. La documentation MDN sur l’égalité des valeurs le confirme. La conséquence pratique est un piège lorsque votre ensemble contient des objets :
new Set([{ id: 1 }]).intersection(new Set([{ id: 1 }]));
// Set(0) {}
L’égalité par référence est le piège : cela retourne un ensemble vide car les deux objets {id: 1} sont des références distinctes, même s’ils semblent identiques. La solution consiste à effectuer des intersections sur des ensembles d’identifiants primitifs stables, puis à reconstituer les objets à partir d’une map de correspondance :
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" }]
Les bugs d’égalité par référence de ce type ne laissent aucune trace dans la stack. L’opération sur l’ensemble se termine sans erreur, retourne un ensemble vide, et l’interface ne se met tout simplement pas à jour — un composant multi-sélection qui ne change pas, un badge de permission qui ne disparaît pas, une vue de diff qui n’affiche aucune modification. En l’absence d’exception, ces erreurs ne remontent pas dans les outils de monitoring. Le session replay est une technique qui rend visible cette catégorie de bugs à échec silencieux : vous observez l’utilisateur interagir, l’opération se terminer, et l’interface ne répondre à rien.
Performance : pourquoi cette approche surpasse filter + includes
Remplacer [...a].filter(x => b.includes(x)) par a.intersection(b) n’est pas seulement un gain en lisibilité. Array.includes() est O(n), ce qui rend le pattern avec filter O(n²) sur deux collections de n éléments, tandis qu’intersection() passe à l’échelle avec le plus petit des deux ensembles, en supposant l’accès moyen sous-linéaire à Set exigé par la spécification. La section sur les objets Set d’ECMA-262 impose que l’accès à Set soit sous-linéaire en moyenne — sans garantir O(1) — et la référence MDN d’intersection() décrit le comportement de passage à l’échelle avec le plus petit ensemble.
| Opération | Pattern avec tableaux | Complexité | Méthode Set native | Complexité |
|---|---|---|---|---|
| Intersection | [...a].filter(x => b.includes(x)) | O(n²) | a.intersection(b) | passe à l’échelle avec le plus petit ensemble, accès moyen sous-linéaire |
| Union | new Set([...a, ...b]) | O(n + m) | a.union(b) | O(n + m) |
| Différence | [...a].filter(x => !b.includes(x)) | O(n²) | a.difference(b) | passe à l’échelle avec a, accès moyen sous-linéaire |
L’écart se creuse avec la taille des collections : sur quelques éléments, il est négligeable, mais les patterns avec tableaux dégradent de façon quadratique tandis que les méthodes natives ne le font pas.
Recettes de migration
Faites correspondre votre code existant de comparaison de tableaux aux nouvelles méthodes directement. Ces trois substitutions couvrent la grande majorité des cas réels.
| Ancien pattern | Nouvelle méthode |
|---|---|
[...a].filter(x => b.includes(x)) | a.intersection(b) |
[...new Set([...a, ...b])] | a.union(b) (retourne un Set) |
[...a].filter(x => !b.includes(x)) | a.difference(b) |
Si vos données se trouvent dans des tableaux, enveloppez chaque côté dans new Set(...) une fois, effectuez l’opération, puis convertissez en tableau si une API en aval en a besoin :
const a = ["x", "y", "z"];
const b = ["y", "z", "w"];
const common = [...new Set(a).intersection(new Set(b))];
// ["y", "z"]
La conversion en Set est elle-même O(n), mais l’aller-retour évite le parcours imbriqué requis par le pattern O(n²) filter + includes et passe mieux à l’échelle à mesure que les collections grandissent.
Compatibilité navigateur et polyfills
Les sept méthodes Set sont Baseline Newly Available depuis le 11 juin 2024, disponibles dans Chrome 122 (20 fév. 2024), Edge 122 (23 fév. 2024), Firefox 127 (11 juin 2024) et Safari 17 (18 sep. 2023), sans polyfill requis pour les cibles navigateur actuelles. Elles sont également disponibles dans Node.js 22.0.0 (24 avr. 2024) via V8 12.4.
| Environnement | Version | Date de sortie |
|---|---|---|
| Chrome | 122 | 20 fév. 2024 |
| Edge | 122 | 23 fév. 2024 |
| Firefox | 127 | 11 juin 2024 |
| Safari | 17 | 18 sep. 2023 |
| Node.js | 22.0.0 (V8 12.4) | 24 avr. 2024 |
Pour les cibles plus anciennes, la bibliothèque core-js et le projet es-shims fournissent des polyfills conformes à la spécification. Si vous ne ciblez que les navigateurs evergreen actuels et Node.js 22+, vous pouvez supprimer le polyfill entièrement.
Pour aller plus loin
Auditez votre base de code à la recherche de combinaisons filter + includes et filter + !includes sur des données dédupliquées — ce sont les candidats directs pour intersection() et difference(). Avant de migrer des collections contenant des objets, convertissez-les en ensembles d’identifiants primitifs stables pour éviter le piège de l’égalité par référence, et n’oubliez pas que toute Map, collection immuable ou index personnalisé exposant size, has et keys peut être passé directement en argument. Les méthodes sont Baseline, la sémantique est stable dans la spécification, et les patterns avec tableaux qu’elles remplacent n’ont jamais été aussi peu coûteux qu’ils en avaient l’air.
FAQ
Puis-je passer une Map directement à Set.intersection() ou dois-je la convertir au préalable ?
Vous pouvez passer une Map directement. Les nouvelles méthodes Set acceptent tout objet Set-like, défini dans la spécification ECMA-262 comme un objet disposant d'une propriété size numérique, d'une méthode has appelable et d'une méthode keys appelable. Une Map satisfait nativement à ces exigences, donc mySet.intersection(myMap) vérifie par rapport aux clés de la map sans construire un new Set(map.keys()) intermédiaire. Les ensembles de bibliothèques de collections immuables et les objets d'index personnalisés exposant ces trois membres fonctionnent également sans conversion.
Pourquoi l'intersection de Sets retourne-t-elle un ensemble vide quand les deux ensembles contiennent des objets identiques ?
Parce que Set compare les valeurs par référence, et non par structure. L'expression new Set([{id:1}]).intersection(new Set([{id:1}])) retourne un ensemble vide car les deux objets {id:1} sont des références distinctes même s'ils semblent identiques, comme confirmé dans Node v22.16.0. La solution consiste à construire des Sets d'identifiants primitifs stables, à effectuer l'opération sur ceux-ci, puis à reconstituer les objets correspondants à partir d'une Map de correspondance indexée par identifiant.
Quelle est la différence entre difference() et symmetricDifference() ?
difference() est directionnelle et non commutative : a.difference(b) retourne les éléments de a absents de b, tandis que b.difference(a) retourne l'inverse. symmetricDifference() retourne les éléments présents dans l'un ou l'autre ensemble mais pas dans les deux, et son contenu est indépendant de l'ordre. Leurs ordres d'itération diffèrent également : symmetricDifference() retourne d'abord les éléments propres au récepteur dans l'ordre du récepteur, suivis des éléments propres à l'autre ensemble dans l'ordre de other.keys(), de sorte que l'ensemble des résultats est identique quel que soit le côté depuis lequel vous l'appelez, mais l'ordre, lui, ne l'est pas.
Ai-je encore besoin d'un polyfill pour les nouvelles méthodes Set en production ?
Pas pour les cibles navigateur actuelles. Les sept méthodes sont Baseline Newly Available depuis le 11 juin 2024, disponibles dans Chrome 122, Edge 122, Firefox 127, Safari 17 et Node.js 22.0.0 via V8 12.4. Si vous ne ciblez que les navigateurs evergreen actuels et Node.js 22 ou ultérieur, vous pouvez supprimer le polyfill entièrement. Pour les cibles plus anciennes, la bibliothèque core-js et le projet es-shims fournissent des polyfills conformes à la spécification que vous pouvez inclure de façon sélective.
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