Menu

JavaScript Symbols: Symbol.iterator & Well-Known Symbols

Was Symbols in JavaScript sind, wozu es sie überhaupt gibt und wie Well-Known Symbols wie Symbol.iterator eigene Objekte an Sprachfeatures andocken.

Symbol – ein garantiert einzigartiger Wert

Symbol ist einer der primitiven Datentypen in JavaScript – neben string, number, boolean, null, undefined und bigint. Das Besondere daran ist schnell erklärt: Jedes Symbol, das du erzeugst, unterscheidet sich von jedem anderen Symbol – und zwar für immer.

index.js
Output
Click Run to see the output here.

Zwei Symbols, beide ohne Argumente erzeugt – und trotzdem nicht gleich. Das ist kein Zufall, sondern genau der Sinn der Sache. Ein Symbol lässt sich nicht fälschen, nicht versehentlich nachbauen und kollidiert auch nicht mit dem Symbol von jemand anderem.

Für Debugging-Zwecke kannst du eine Beschreibung mitgeben. Auf die Identität hat das keinen Einfluss:

index.js
Output
Click Run to see the output here.

Gleiche Beschreibung, unterschiedliche Symbole. Die Beschreibung ist ausschließlich ein Label für Menschen, die Logs lesen.

Warum es Symbols gibt: kollisionsfreie Keys

Der eigentliche Grund, warum Symbols überhaupt existieren: Du kannst damit Eigenschaften an ein Objekt hängen, ohne dir Sorgen zu machen, dass dein Key mit dem von jemand anderem kollidiert. String-Keys teilen sich alle denselben flachen Namespace — wenn zwei Libraries ihre Metadaten beide unter obj.meta ablegen wollen, überschreiben sie sich gegenseitig. Symbol-Keys haben dieses Problem nicht, denn dein Symbol hat sonst niemand.

index.js
Output
Click Run to see the output here.

Die eckigen Klammern in [ID]: 42 sind die Syntax für berechnete Eigenschaftsnamen (computed properties) – sie sagen: „Nimm den Wert von ID als Schlüssel." Nur Code, der dasselbe ID-Symbol in Händen hält, kann diese Eigenschaft lesen oder überschreiben. Ein anderes Modul, das den String "id" oder sein eigenes Symbol("id") verwendet, spricht über ein völlig anderes Slot.

Symbol-Keys bleiben (weitgehend) verborgen

Eigenschaften mit Symbol als Key tauchen weder in for...in noch in Object.keys oder JSON.stringify auf. Geheim sind sie nicht – wer unbedingt will, findet sie trotzdem – aber bei der normalen Iteration stören sie nicht.

index.js
Output
Click Run to see the output here.

Object.keys und JSON.stringify überspringen Symbol-Keys komplett. Willst du gezielt an die Symbol-Properties rankommen, helfen dir Object.getOwnPropertySymbols und Reflect.ownKeys – damit werden sie sichtbar. Genau dieses Verhalten willst du für Metadaten: sichtbar, wenn du aktiv danach suchst, unsichtbar für Code, der einfach nur über String-Keys läuft.

Symbole teilen mit Symbol.for

Mit Symbol() bekommst du ein lokales, einmaliges Symbol. Manchmal brauchst du aber das Gegenteil – ein Symbol, das im ganzen Programm identisch ist, über Modulgrenzen hinweg, damit sich verschiedene Codestellen auf denselben Key einigen können. Genau dafür gibt es Symbol.for.

index.js
Output
Click Run to see the output here.

Symbol.for(key) schaut in einer globalen Registry nach: Existiert dort bereits ein Symbol für diesen Key, bekommst du genau dieses zurück; andernfalls wird eins erzeugt und gespeichert. Symbol.keyFor geht den umgekehrten Weg — es liefert den Key, mit dem ein registriertes Symbol erstellt wurde (bzw. undefined bei nicht registrierten).

In den meisten Fällen reicht dir das schlichte Symbol(). Zu Symbol.for solltest du nur greifen, wenn ein Symbol tatsächlich über Modulgrenzen hinweg geteilt werden muss.

Well-Known Symbols: Einhängepunkte in die Sprache

Jetzt wird's spannend — hier zeigen Symbols ihre wahre Stärke. JavaScript bringt eine Reihe vordefinierter Symbols mit, die sogenannten Well-Known Symbols, nach denen die Sprache selbst auf deinen Objekten sucht. Implementierst du eine Methode unter einem dieser Keys, dockt dein Objekt direkt an ein eingebautes Sprachfeature an.

Das mit Abstand nützlichste ist Symbol.iterator. Jedes Objekt, das eine Methode unter diesem Key bereitstellt, gilt als iterierbar — es funktioniert dann mit for...of, Spread, Destructuring und Array.from.

index.js
Output
Click Run to see the output here.

range ist kein Array. Es ist ein schlichtes Objekt mit einer einzigen, speziell benannten Methode. Weil diese Methode aber einen Iterator zurückgibt, funktionieren for...of und der Spread-Operator problemlos damit — genauso wie bei Arrays, Strings oder Map. Genau darin besteht der Deal: Wer Symbol.iterator implementiert, gilt für die Sprache als iterierbar.

Weitere Well-Known Symbols, die du kennen solltest

Es gibt noch eine Handvoll mehr — jedes davon ein Einstiegspunkt in einen anderen Teil der Sprache:

index.js
Output
Click Run to see the output here.
  • Symbol.iterator – macht ein Objekt iterierbar.
  • Symbol.asyncIterator – dasselbe, aber für for await...of.
  • Symbol.toPrimitive – steuert, wie ein Objekt in einem Coercion-Kontext in einen Primitivwert (Number, String oder Default) umgewandelt wird.
  • Symbol.hasInstance – passt an, was instanceof für deine Klasse zurückgibt.
  • Symbol.toStringTag – legt das Tag fest, das in Object.prototype.toString.call(obj) erscheint.

Auswendig lernen musst du die nicht. Es reicht zu wissen, dass es sie gibt – sobald du möchtest, dass sich dein Objekt wie ein eingebauter Typ verhält, findest du in der Regel ein passendes Well-known Symbol.

Symbols sind keine Strings

Ein typischer Stolperstein: Symbols werden nicht automatisch in Strings umgewandelt. Eine Konkatenation wirft einen Fehler, und genauso eine API, die einen String erwartet:

index.js
Output
Click Run to see the output here.

Template Literals werfen denselben TypeError. Wenn du tatsächlich Text willst, nimm String(symbol) oder symbol.toString(). Diese Ecken und Kanten sind Absicht – die Sprache bewahrt dich davor, einen eindeutigen Identitätswert versehentlich wie ein gewöhnliches Stück String-Daten zu behandeln.

Wann du Symbols einsetzen solltest – und wann nicht

Symbols lohnen sich, wenn:

  • Du Metadaten an Objekte hängst, die dir nicht gehören, und einen Key brauchst, der garantiert nicht kollidiert.
  • Du ein Protokoll entwirfst – nach dem Motto: „Objekte, die mit meiner Library zusammenarbeiten wollen, sollen eine Methode unter diesem Symbol implementieren."
  • Du eine Property haben willst, die weder in JSON.stringify noch in for...in auftaucht.
  • Du Symbol.iterator oder ein anderes Well-Known Symbol implementierst.

Finger weg von Symbols, wenn:

  • Du einfach nur einen Object-Key brauchst und keine Kollisionsgefahr besteht. Ein String ist schlichter und erscheint in Logs so, wie er ist.
  • Du „private" Felder haben möchtest. Properties mit Symbol-Keys sind nicht wirklich privat – Object.getOwnPropertySymbols findet sie trotzdem. Für echte Privatheit nimm #private-Klassenfelder.
  • Du Daten speicherst, die JSON.stringify überleben müssen. Tun sie nicht.

Der meiste JavaScript-Code kommt völlig ohne ein direktes Symbol(...) aus. Sobald du aber willst, dass dein eigenes Objekt mit for...of funktioniert oder sich in einem Coercion-Kontext natürlich verhält, sind Symbols genau die Tür, die dich in diese Mechanik hineinlässt.

Weiter geht's: Funktionsdeklarationen

Symbols, Iteratoren und Generatoren hängen alle stark an Funktionen – die Methoden, die unter Symbol.iterator abgelegt sind, die Factories, die Iteratoren zurückgeben, die Generatoren, deren function*-Form den größten Teil des Boilerplates verschluckt. Funktionen sind das nächste Kapitel, beginnend mit den verschiedenen Arten, sie zu deklarieren, und damit, was sich zwischen den einzelnen Formen ändert.

Häufig gestellte Fragen

Was ist ein Symbol in JavaScript?

Ein Symbol ist ein primitiver Datentyp, dessen Werte garantiert eindeutig sind. Erzeugt wird eins mit Symbol() oder Symbol('Beschreibung') — zwei Aufrufe liefern nie dasselbe Symbol zurück. Verwendet werden sie vor allem als Objekt-Keys, die sich mit keinem anderen Key beißen, und als Einstiegspunkte in Sprachfeatures über Well-Known Symbols wie Symbol.iterator.

Wann nimmt man ein Symbol statt einem String als Key?

Immer dann, wenn man eine Property an ein Objekt hängen will, ohne sich mit fremdem Code um denselben Namen zu streiten — also zum Beispiel Libraries, die Metadaten anhängen, Frameworks, die Objekte taggen, oder Keys, die bewusst nicht zur öffentlichen API gehören sollen. Für normale, lesbare Keys ohne Kollisionsrisiko bleiben Strings weiterhin die erste Wahl.

Wofür ist Symbol.iterator gut?

Symbol.iterator ist ein Well-Known Symbol, das for...of, Spread-Operator und Destrukturierung verrät, wie ein Objekt durchlaufen werden soll. Legt man unter dem Key Symbol.iterator eine Methode ab, die einen Iterator zurückgibt, wird das Objekt iterierbar. Genau so funktionieren intern übrigens Arrays, Strings, Map und Set.

Was ist der Unterschied zwischen Symbol() und Symbol.for()?

Symbol('x') erzeugt jedes Mal ein frisches Symbol — zwei Aufrufe mit derselben Beschreibung sind trotzdem unterschiedlich. Symbol.for('x') schaut dagegen in einer globalen Registry nach und gibt für denselben Key programmweit dasselbe Symbol zurück. Symbol.for nimmt man also, wenn Symbols über Module oder Realms hinweg geteilt werden sollen; Symbol() dann, wenn es nur lokal eindeutig sein muss.

Lerne mit Coddy zu programmieren

LOS GEHT'S