Le type Symbol : une valeur unique par construction
En JavaScript, Symbol est un type primitif, au même titre que string, number, boolean, null, undefined et bigint. Sa caractéristique fondamentale tient en une phrase : chaque symbole que vous créez est distinct de tous les autres, pour toujours.
Deux symboles, tous les deux créés sans argument, et pourtant ils ne sont pas égaux. Ce n'est pas un bug — c'est justement tout l'intérêt du type primitif Symbol. Impossible de fabriquer un symbole en double, de le recréer par hasard ou d'entrer en collision avec celui de quelqu'un d'autre.
On peut toutefois lui associer une description, bien pratique pour le débogage. Elle n'a aucun impact sur l'identité du symbole :
Même description, symboles différents. La description n'est qu'une étiquette destinée aux humains qui lisent les logs.
Pourquoi les symbols existent : des clés uniques sans collision
La raison d'être du type primitif Symbol, c'est de te permettre d'ajouter des propriétés à un objet sans craindre qu'une de tes clés entre en conflit avec celle de quelqu'un d'autre. Les clés de type string partagent toutes le même espace de noms : si deux bibliothèques décident de stocker leurs métadonnées dans obj.meta, elles s'écrasent mutuellement. Avec une clé unique de type symbol, ce problème disparaît, tout simplement parce que personne d'autre ne possède ton symbol.
Les crochets dans [ID]: 42 correspondent à la syntaxe des propriétés calculées : ils signifient « utilise la valeur de ID comme clé ». Seul un bout de code qui détient ce même symbole ID peut lire ou écraser cette propriété. Un autre module qui utilise la chaîne "id" ou son propre Symbol("id") parle d'un emplacement complètement différent.
Les clés de type Symbol sont (presque) invisibles
Les propriétés dont la clé est un Symbol n'apparaissent ni dans for...in, ni dans Object.keys, ni dans JSON.stringify. Rien de secret là-dedans : du code motivé saura toujours les retrouver, mais elles restent discrètes lors des itérations classiques.
Object.keys et JSON.stringify ignorent purement et simplement les clés de type symbol. Si vous voulez vraiment récupérer les propriétés dont la clé est un symbol, Object.getOwnPropertySymbols et Reflect.ownKeys les exposent. C'est exactement le comportement qu'on attend pour des métadonnées : visibles quand on les cherche, invisibles pour le code qui se contente de parcourir les clés string.
Partager des symbols avec Symbol.for
Symbol() crée un symbol local, unique et jetable. Parfois, on veut l'inverse : un symbol qui reste le même partout dans le programme, d'un module à l'autre, pour que différentes parties du code puissent s'accorder sur une même clé. C'est précisément le rôle de Symbol.for.
Symbol.for(key) consulte un registre global : si un symbole existe déjà pour cette clé, tu le récupères ; sinon, il est créé et stocké. Symbol.keyFor fait l'inverse — il te renvoie la clé avec laquelle un symbole enregistré a été créé (ou undefined pour ceux qui ne le sont pas).
Dans la majorité des cas, un simple Symbol() suffit largement. Symbol.for se justifie uniquement quand un symbole doit vraiment être partagé entre plusieurs modules.
Les well-known symbols : des points d'accroche dans le langage
C'est là que les symbols prennent tout leur sens. JavaScript expose une série de symbols prédéfinis — les well-known symbols — que le langage lui-même va chercher sur tes objets. Il suffit d'implémenter une méthode associée à l'une de ces clés pour que ton objet se branche directement sur une fonctionnalité native.
Le plus utile, c'est Symbol.iterator. Tout objet qui possède une méthode sur cette clé devient itérable : il fonctionne avec for...of, le spread, la déstructuration et Array.from.
range n'est pas un tableau. C'est un simple objet avec une méthode dont la clé est un peu spéciale. Mais comme cette méthode renvoie un itérateur, for...of et le spread fonctionnent dessus — exactement comme sur les tableaux, les chaînes ou les Map. C'est tout le principe du contrat : implémentez Symbol.iterator, et le langage vous considère comme itérable.
Les autres well-known symbols à connaître
Il en existe quelques autres, chacun servant de point d'accroche à une partie différente du langage :
Symbol.iterator— pour rendre un objet itérable en JavaScript.Symbol.asyncIterator— pareil, mais pourfor await...of.Symbol.toPrimitive— contrôle la manière dont un objet est converti en primitive (nombre, chaîne ou valeur par défaut) lors d'une coercition.Symbol.hasInstance— personnalise le résultat deinstanceofpour votre classe.Symbol.toStringTag— définit l'étiquette retournée parObject.prototype.toString.call(obj).
Pas besoin de les retenir par cœur. Savoir qu'ils existent suffit amplement : le jour où vous voudrez qu'un de vos objets se comporte comme un type natif, il y aura très probablement un well-known symbol fait pour ça.
Un symbol n'est pas une chaîne
Un piège classique : les symbols ne se convertissent pas automatiquement en chaîne. Les concaténer déclenche une erreur, et c'est pareil si vous les passez à une API qui attend une string :
Les template literals déclenchent la même TypeError. Si tu veux réellement obtenir du texte, passe par String(symbol) ou symbol.toString(). Ces aspérités sont voulues : le langage t'empêche de traiter par mégarde une valeur d'identité unique comme une simple chaîne de caractères.
Quand utiliser les symbols (et quand s'en passer)
Les symbols sont pertinents quand :
- Tu veux attacher des métadonnées à des objets qui ne t'appartiennent pas, avec une clé garantie sans collision.
- Tu conçois un protocole — « les objets qui veulent fonctionner avec ma librairie doivent implémenter une méthode à ce symbol ».
- Tu cherches une propriété qui n'apparaîtra ni dans
JSON.stringify, ni dans unfor...in. - Tu implémentes
Symbol.iteratorou un autre well-known symbol.
En revanche, laisse tomber les symbols quand :
- Tu as juste besoin d'une clé d'objet et qu'aucune collision n'est à craindre. Une string, c'est plus simple, et elle s'affiche telle quelle dans les logs.
- Tu veux des champs « privés ». Les propriétés à clé symbol ne sont pas vraiment privées —
Object.getOwnPropertySymbolsles retrouve. Pour de la vraie confidentialité, utilise les champs de classe#private. - Tu stockes des données qui doivent survivre à un
JSON.stringify. Elles ne passeront pas.
La plupart du code JavaScript vit très bien sans jamais écrire Symbol(...) directement. Mais dès que tu veux que ton propre objet fonctionne avec for...of ou se comporte naturellement dans un contexte de coercion, les symbols sont la porte d'entrée vers toute cette mécanique.
La suite : les déclarations de fonctions
Les symbols, les itérateurs et les générateurs reposent massivement sur les fonctions — les méthodes stockées à Symbol.iterator, les factories qui retournent des itérateurs, les générateurs dont la forme function* masque l'essentiel du code répétitif. Les fonctions, c'est le prochain chapitre : on commencera par les différentes façons de les déclarer et par ce qui change d'une forme à l'autre.
Questions fréquentes
Qu'est-ce qu'un symbol en JavaScript ?
Un symbol est un type primitif dont chaque valeur est garantie unique. On en crée un avec Symbol() ou Symbol('description'), et deux appels ne produiront jamais de symbols égaux. On s'en sert principalement comme clés d'objet qui n'entrent pas en collision avec d'autres clés, et comme points d'accroche aux mécaniques du langage via les well-known symbols comme Symbol.iterator.
Quand utiliser un symbol plutôt qu'une clé string ?
Utilisez un symbol quand vous voulez ajouter une propriété à un objet sans risquer de collision avec du code tiers — typiquement une librairie qui attache des métadonnées, un framework qui tague des objets, ou une clé volontairement hors de l'API publique. Pour des clés du quotidien où la lisibilité prime et où les collisions ne sont pas un souci, les strings restent le bon choix.
À quoi sert Symbol.iterator ?
Symbol.iterator est un well-known symbol qui indique à for...of, au spread et à la destructuration comment parcourir un objet. Si vous définissez une méthode sur la clé Symbol.iterator qui renvoie un iterator, votre objet devient itérable. C'est exactement comme ça que fonctionnent les arrays, les strings, Map et Set en interne.
Quelle est la différence entre Symbol() et Symbol.for() ?
Symbol('x') crée un nouveau symbol à chaque appel — deux appels avec la même description produisent quand même deux symbols distincts. Symbol.for('x'), lui, consulte un registre global et renvoie le même symbol pour une clé donnée dans tout le programme. Utilisez Symbol.for quand vous avez besoin de partager un symbol entre modules ou realms ; utilisez Symbol() pour une unicité purement locale.