7. Symbole
Inhaltsverzeichnis
Bitte unterstützen Sie dieses Buch: kaufen Sie es (PDF, EPUB, MOBI) oder spenden Sie
(Werbung, bitte nicht blockieren.)

7. Symbole



7.1 Übersicht

Symbole sind ein neuer primitiver Datentyp in ECMAScript 6. Sie werden über eine Factory-Funktion erstellt.

const mySymbol = Symbol('mySymbol');

Jedes Mal, wenn Sie die Factory-Funktion aufrufen, wird ein neues und eindeutiges Symbol erstellt. Der optionale Parameter ist eine beschreibende Zeichenkette, die beim Drucken des Symbols angezeigt wird (sie hat keinen anderen Zweck).

> mySymbol
Symbol(mySymbol)

7.1.1 Anwendungsfall 1: eindeutige Eigenschaftsschlüssel

Symbole werden hauptsächlich als eindeutige Eigenschaftsschlüssel verwendet – ein Symbol kollidiert niemals mit einem anderen Eigenschaftsschlüssel (Symbol oder String). Sie können beispielsweise ein Objekt *iterierbar* machen (verwendbar über die for-of-Schleife und andere Sprachmechanismen), indem Sie das in Symbol.iterator gespeicherte Symbol als Schlüssel einer Methode verwenden (mehr Informationen zu Iterables finden Sie in dem Kapitel über Iteration).

const iterableObject = {
    [Symbol.iterator]() { // (A)
        ···
    }
}
for (const x of iterableObject) {
    console.log(x);
}
// Output:
// hello
// world

In Zeile A wird ein Symbol als Schlüssel der Methode verwendet. Diese eindeutige Markierung macht das Objekt iterierbar und ermöglicht uns die Verwendung der for-of-Schleife.

7.1.2 Anwendungsfall 2: Konstanten, die Konzepte repräsentieren

In ECMAScript 5 haben Sie möglicherweise Strings verwendet, um Konzepte wie Farben darzustellen. In ES6 können Sie Symbole verwenden und sicher sein, dass sie immer eindeutig sind.

const COLOR_RED    = Symbol('Red');
const COLOR_ORANGE = Symbol('Orange');
const COLOR_YELLOW = Symbol('Yellow');
const COLOR_GREEN  = Symbol('Green');
const COLOR_BLUE   = Symbol('Blue');
const COLOR_VIOLET = Symbol('Violet');

function getComplement(color) {
    switch (color) {
        case COLOR_RED:
            return COLOR_GREEN;
        case COLOR_ORANGE:
            return COLOR_BLUE;
        case COLOR_YELLOW:
            return COLOR_VIOLET;
        case COLOR_GREEN:
            return COLOR_RED;
        case COLOR_BLUE:
            return COLOR_ORANGE;
        case COLOR_VIOLET:
            return COLOR_YELLOW;
        default:
            throw new Exception('Unknown color: '+color);
    }
}

Jedes Mal, wenn Sie Symbol('Red') aufrufen, wird ein neues Symbol erstellt. Daher kann COLOR_RED niemals mit einem anderen Wert verwechselt werden. Das wäre anders, wenn es sich um den String 'Red' handeln würde.

7.1.3 Fallstrick: Symbole können nicht in Strings umgewandelt werden

Das Umwandeln (implizite Konvertierung) von Symbolen in Strings löst Ausnahmen aus.

const sym = Symbol('desc');

const str1 = '' + sym; // TypeError
const str2 = `${sym}`; // TypeError

Die einzige Lösung ist die explizite Umwandlung.

const str2 = String(sym); // 'Symbol(desc)'
const str3 = sym.toString(); // 'Symbol(desc)'

Das Verhindern der Konvertierung verhindert einige Fehler, macht aber auch die Arbeit mit Symbolen komplizierter.

Die folgenden Operationen sind sich Symbole als Eigenschaftsschlüssel bewusst:

Die folgenden Operationen ignorieren Symbole als Eigenschaftsschlüssel:

7.2 Ein neuer primitiver Typ

ECMAScript 6 führt einen neuen primitiven Datentyp ein: Symbole. Sie sind Tokens, die als eindeutige IDs dienen. Sie erstellen Symbole über die Factory-Funktion Symbol() (die lose String ähnelt, indem sie Strings zurückgibt, wenn sie als Funktion aufgerufen wird).

const symbol1 = Symbol();

Symbol() hat einen optionalen Parameter vom Typ String, mit dem Sie dem neu erstellten Symbol eine Beschreibung geben können. Diese Beschreibung wird verwendet, wenn das Symbol in einen String umgewandelt wird (über toString() oder String()).

> const symbol2 = Symbol('symbol2');
> String(symbol2)
'Symbol(symbol2)'

Jedes von Symbol() zurückgegebene Symbol ist eindeutig, jedes Symbol hat seine eigene Identität.

> Symbol() === Symbol()
false

Sie können sehen, dass Symbole primitiv sind, wenn Sie den Operator typeof darauf anwenden – er gibt ein neues, Symbol-spezifisches Ergebnis zurück.

> typeof Symbol()
'symbol'

7.2.1 Symbole als Eigenschaftsschlüssel

Symbole können als Eigenschaftsschlüssel verwendet werden.

const MY_KEY = Symbol();
const obj = {};

obj[MY_KEY] = 123;
console.log(obj[MY_KEY]); // 123

Klassen und Objektliterale haben eine Funktion namens *berechnete Eigenschaftsschlüssel*: Sie können den Schlüssel einer Eigenschaft über einen Ausdruck angeben, indem Sie ihn in eckige Klammern setzen. Im folgenden Objektliteral verwenden wir einen berechneten Eigenschaftsschlüssel, um den Wert von MY_KEY zum Schlüssel einer Eigenschaft zu machen.

const MY_KEY = Symbol();
const obj = {
    [MY_KEY]: 123
};

Eine Methodendefinition kann ebenfalls einen berechneten Schlüssel haben.

const FOO = Symbol();
const obj = {
    [FOO]() {
        return 'bar';
    }
};
console.log(obj[FOO]()); // bar

7.2.2 Aufzählung eigener Eigenschaftsschlüssel

Da es nun eine neue Art von Wert gibt, die zum Schlüssel einer Eigenschaft werden kann, wird für ECMAScript 6 die folgende Terminologie verwendet:

Betrachten wir die APIs zur Aufzählung eigener Eigenschaftsschlüssel, indem wir zunächst ein Objekt erstellen.

const obj = {
    [Symbol('my_key')]: 1,
    enum: 2,
    nonEnum: 3
};
Object.defineProperty(obj,
    'nonEnum', { enumerable: false });

Object.getOwnPropertyNames() ignoriert symbolbasierte Eigenschaftsschlüssel.

> Object.getOwnPropertyNames(obj)
['enum', 'nonEnum']

Object.getOwnPropertySymbols() ignoriert stringbasierte Eigenschaftsschlüssel.

> Object.getOwnPropertySymbols(obj)
[Symbol(my_key)]

Reflect.ownKeys() berücksichtigt alle Arten von Schlüsseln.

> Reflect.ownKeys(obj)
[Symbol(my_key), 'enum', 'nonEnum']

Object.keys() berücksichtigt nur aufzählbare Eigenschaftsschlüssel, die Strings sind.

> Object.keys(obj)
['enum']

Der Name Object.keys kollidiert mit der neuen Terminologie (nur Schlüssel vom Typ String werden aufgelistet). Object.names oder Object.getEnumerableOwnPropertyNames wären jetzt eine bessere Wahl.

7.3 Verwendung von Symbolen zur Darstellung von Konzepten

In ECMAScript 5 repräsentiert man Konzepte (denken Sie an Enum-Konstanten) oft über Strings. Zum Beispiel:

var COLOR_RED    = 'Red';
var COLOR_ORANGE = 'Orange';
var COLOR_YELLOW = 'Yellow';
var COLOR_GREEN  = 'Green';
var COLOR_BLUE   = 'Blue';
var COLOR_VIOLET = 'Violet';

Strings sind jedoch nicht so eindeutig, wie wir es gerne hätten. Um zu sehen, warum, betrachten wir die folgende Funktion.

function getComplement(color) {
    switch (color) {
        case COLOR_RED:
            return COLOR_GREEN;
        case COLOR_ORANGE:
            return COLOR_BLUE;
        case COLOR_YELLOW:
            return COLOR_VIOLET;
        case COLOR_GREEN:
            return COLOR_RED;
        case COLOR_BLUE:
            return COLOR_ORANGE;
        case COLOR_VIOLET:
            return COLOR_YELLOW;
        default:
            throw new Exception('Unknown color: '+color);
    }
}

Es ist bemerkenswert, dass beliebige Ausdrücke als switch-Fälle verwendet werden können, man ist in keiner Weise eingeschränkt. Zum Beispiel:

function isThree(x) {
    switch (x) {
        case 1 + 1 + 1:
            return true;
        default:
            return false;
    }
}

Wir nutzen die Flexibilität, die uns switch bietet, und beziehen uns auf die Farben über unsere Konstanten (COLOR_RED usw.) anstatt sie fest zu codieren ('Red' usw.).

Interessanterweise kann es, auch wenn wir dies tun, immer noch zu Verwechslungen kommen. Jemand könnte zum Beispiel eine Konstante für eine Stimmung definieren:

var MOOD_BLUE = 'Blue';

Nun ist der Wert von COLOR_BLUE nicht mehr eindeutig und MOOD_BLUE kann damit verwechselt werden. Wenn Sie ihn als Parameter für getComplement() verwenden, gibt er 'Orange' zurück, wo er eine Ausnahme auslösen sollte.

Verwenden wir Symbole, um dieses Beispiel zu beheben. Nun können wir auch das ES6-Feature const verwenden, mit dem wir tatsächliche Konstanten deklarieren können (Sie können nicht ändern, welcher Wert an eine Konstante gebunden ist, aber der Wert selbst kann veränderlich sein).

const COLOR_RED    = Symbol('Red');
const COLOR_ORANGE = Symbol('Orange');
const COLOR_YELLOW = Symbol('Yellow');
const COLOR_GREEN  = Symbol('Green');
const COLOR_BLUE   = Symbol('Blue');
const COLOR_VIOLET = Symbol('Violet');

Jeder von Symbol zurückgegebene Wert ist eindeutig, weshalb kein anderer Wert nun mit BLUE verwechselt werden kann. Interessanterweise ändert sich der Code von getComplement() überhaupt nicht, wenn wir Symbole anstelle von Strings verwenden, was zeigt, wie ähnlich sie sind.

7.4 Symbole als Schlüssel von Eigenschaften

Die Möglichkeit, Eigenschaften zu erstellen, deren Schlüssel niemals mit anderen Schlüsseln kollidieren, ist in zwei Situationen nützlich:

7.4.1 Symbole als Schlüssel nicht-öffentlicher Eigenschaften

Immer wenn es Vererbungshierarchien in JavaScript gibt (z. B. erstellt über Klassen, Mixins oder einen rein prototypischen Ansatz), haben Sie zwei Arten von Eigenschaften:

Aus Gründen der Benutzerfreundlichkeit haben öffentliche Eigenschaften normalerweise String-Schlüssel. Aber bei privaten Eigenschaften mit String-Schlüsseln können versehentliche Namenskonflikte zum Problem werden. Daher sind Symbole eine gute Wahl. Im folgenden Code werden beispielsweise Symbole für die privaten Eigenschaften _counter und _action verwendet.

const _counter = Symbol('counter');
const _action = Symbol('action');
class Countdown {
    constructor(counter, action) {
        this[_counter] = counter;
        this[_action] = action;
    }
    dec() {
        let counter = this[_counter];
        if (counter < 1) return;
        counter--;
        this[_counter] = counter;
        if (counter === 0) {
            this[_action]();
        }
    }
}

Beachten Sie, dass Symbole Sie nur vor Namenskonflikten schützen, nicht vor unbefugtem Zugriff, da Sie alle eigenen Eigenschaftsschlüssel – einschließlich Symbole – eines Objekts über Reflect.ownKeys() ermitteln können. Wenn Sie dort auch Schutz wünschen, können Sie einen der in Abs. „Private Daten für Klassen“ aufgeführten Ansätze verwenden.

7.4.2 Symbole als Schlüssel von Metadaten-Eigenschaften

Da Symbole eindeutige Identitäten haben, sind sie ideal als Schlüssel für öffentliche Eigenschaften, die auf einer anderen Ebene existieren als „normale“ Eigenschaftsschlüssel, da Metadaten-Schlüssel und normale Schlüssel nicht kollidieren dürfen. Ein Beispiel für Metadaten-Eigenschaften sind Methoden, die Objekte implementieren können, um zu steuern, wie sie von einer Bibliothek behandelt werden. Die Verwendung von Symbol-Schlüsseln schützt die Bibliothek davor, normale Methoden als Anpassungsmethoden zu missverstehen.

ES6-Iterierbarkeit ist eine solche Anpassung. Ein Objekt ist *iterierbar*, wenn es eine Methode hat, deren Schlüssel das Symbol (gespeichert in) Symbol.iterator ist. Im folgenden Code ist obj iterierbar.

const obj = {
    data: [ 'hello', 'world' ],
    [Symbol.iterator]() {
        ···
    }
};

Die Iterierbarkeit von obj ermöglicht es Ihnen, die for-of-Schleife und ähnliche JavaScript-Funktionen zu verwenden.

for (const x of obj) {
    console.log(x);
}

// Output:
// hello
// world

7.4.3 Beispiele für Namenskonflikte in der Standardbibliothek von JavaScript

Falls Sie denken, dass Namenskonflikte keine Rolle spielen, hier sind drei Beispiele, wo Namenskonflikte bei der Entwicklung der JavaScript-Standardbibliothek Probleme verursacht haben:

Im Gegensatz dazu kann das Hinzufügen von Iterierbarkeit zu einem Objekt über den Eigenschaftsschlüssel Symbol.iterator keine Probleme verursachen, da dieser Schlüssel mit nichts kollidiert.

7.5 Umwandlung von Symbolen in andere primitive Typen

Die folgende Tabelle zeigt, was passiert, wenn Sie Symbole explizit oder implizit in andere primitive Typen umwandeln:

Umwandlung in Explizite Konvertierung Konvertierung (implizite Umwandlung)
boolean Boolean(sym) → OK !sym → OK
number Number(sym)TypeError sym*2TypeError
string String(sym) → OK ''+symTypeError
  sym.toString() → OK `${sym}`TypeError

7.5.1 Fallstrick: Konvertierung in String

Die verbotene Konvertierung in einen String kann Sie leicht in die Irre führen.

const sym = Symbol();

console.log('A symbol: '+sym); // TypeError
console.log(`A symbol: ${sym}`); // TypeError

Um diese Probleme zu beheben, benötigen Sie eine explizite Umwandlung in einen String.

console.log('A symbol: '+String(sym)); // OK
console.log(`A symbol: ${String(sym)}`); // OK

7.5.2 Sinn und Zweck der Konvertierungsregeln

Konvertierung (implizite Umwandlung) ist für Symbole oft verboten. Dieser Abschnitt erklärt, warum.

7.5.2.1 Wahrheitswertprüfungen sind erlaubt

Die Konvertierung in Boolean ist immer erlaubt, hauptsächlich um Wahrheitswertprüfungen in if-Anweisungen und anderen Stellen zu ermöglichen.

if (value) { ··· }

param = param || 0;
7.5.2.2 Versehentliche Umwandlung von Symbolen in Eigenschaftsschlüssel

Symbole sind spezielle Eigenschaftsschlüssel, weshalb Sie sie nicht versehentlich in Strings umwandeln möchten, die eine andere Art von Eigenschaftsschlüssel sind. Dies könnte passieren, wenn Sie den Additionsoperator verwenden, um den Namen einer Eigenschaft zu berechnen.

myObject['__' + value]

Deshalb wird ein TypeError ausgelöst, wenn value ein Symbol ist.

7.5.2.3 Versehentliche Umwandlung von Symbolen in Array-Indizes

Sie möchten Symbole auch nicht versehentlich in Array-Indizes umwandeln. Der folgende Code zeigt, wo dies passieren könnte, wenn value ein Symbol ist.

myArray[1 + value]

Deshalb löst der Additionsoperator in diesem Fall einen Fehler aus.

7.5.3 Explizite und implizite Konvertierung in der Spezifikation

7.5.3.1 Umwandlung in Boolean

Um ein Symbol explizit in Boolean umzuwandeln, rufen Sie Boolean() auf, was für Symbole true zurückgibt.

> const sym = Symbol('hello');
> Boolean(sym)
true

Boolean() berechnet sein Ergebnis über die interne Operation ToBoolean(), die für Symbole und andere "truthy"-Werte true zurückgibt.

Die Konvertierung verwendet ebenfalls ToBoolean().

> !sym
false
7.5.3.2 Umwandlung in Zahl

Um ein Symbol explizit in eine Zahl umzuwandeln, rufen Sie Number() auf.

> const sym = Symbol('hello');
> Number(sym)
TypeError: can't convert symbol to number

Number() berechnet sein Ergebnis über die interne Operation ToNumber(), die für Symbole einen TypeError auslöst.

Die Konvertierung verwendet ebenfalls ToNumber().

> +sym
TypeError: can't convert symbol to number
7.5.3.3 Umwandlung in String

Um ein Symbol explizit in einen String umzuwandeln, rufen Sie String() auf.

> const sym = Symbol('hello');
> String(sym)
'Symbol(hello)'

Wenn der Parameter von String() ein Symbol ist, behandelt es die Umwandlung in einen String selbst und gibt den String Symbol() umhüllt von der Beschreibung zurück, die bei der Erstellung des Symbols angegeben wurde. Wenn keine Beschreibung angegeben wurde, wird der leere String verwendet.

> String(Symbol())
'Symbol()'

Die Methode toString() gibt denselben String zurück wie String(), aber keine dieser beiden Operationen ruft die andere auf; beide rufen dieselbe interne Operation SymbolDescriptiveString() auf.

> Symbol('hello').toString()
'Symbol(hello)'

Die Konvertierung wird über die interne Operation ToString() gehandhabt, die für Symbole einen TypeError auslöst. Eine Methode, die ihren Parameter in einen String umwandelt, ist Number.parseInt().

> Number.parseInt(Symbol())
TypeError: can't convert symbol to string
7.5.3.4 Nicht erlaubt: Umwandlung über den binären Additionsoperator (+)

Der Additionsoperator funktioniert wie folgt:

Die Umwandlung in String oder Zahl löst eine Ausnahme aus, was bedeutet, dass Sie den Additionsoperator nicht (direkt) für Symbole verwenden können.

> '' + Symbol()
TypeError: can't convert symbol to string
> 1 + Symbol()
TypeError: can't convert symbol to number

7.6 Wrapper-Objekte für Symbole

Während alle anderen primitiven Werte Literale haben, müssen Sie Symbole durch Aufrufen der Funktion Symbol erstellen. Daher besteht die Gefahr, Symbol versehentlich als Konstruktor aufzurufen. Dies erzeugt Instanzen von Symbol, die nicht sehr nützlich sind. Daher wird eine Ausnahme ausgelöst, wenn Sie dies versuchen.

> new Symbol()
TypeError: Symbol is not a constructor

Es gibt immer noch eine Möglichkeit, Wrapper-Objekte, Instanzen von Symbol, zu erstellen: Object, als Funktion aufgerufen, wandelt alle Werte in Objekte um, einschließlich Symbolen.

> const sym = Symbol();
> typeof sym
'symbol'

> const wrapper = Object(sym);
> typeof wrapper
'object'
> wrapper instanceof Symbol
true

7.6.1 Zugriff auf Eigenschaften über [ ] und gewrappte Schlüssel

Der eckige Klammer-Operator [ ] wandelt seinen Operanden normalerweise in einen String um. Es gibt nun zwei Ausnahmen: Symbol-Wrapper-Objekte werden "entpackt" und Symbole werden wie sie sind verwendet. Betrachten wir dieses Objekt, um dieses Phänomen zu untersuchen.

const sym = Symbol('yes');
const obj = {
    [sym]: 'a',
    str: 'b',
};

Der eckige Klammer-Operator entpackt gewrappte Symbole.

> const wrappedSymbol = Object(sym);
> typeof wrappedSymbol
'object'
> obj[wrappedSymbol]
'a'

Wie jeder andere Wert, der nicht mit Symbolen zusammenhängt, wird ein gewrappter String vom eckigen Klammer-Operator in einen String umgewandelt.

> const wrappedString = new String('str');
> typeof wrappedString
'object'
> obj[wrappedString]
'b'
7.6.1.1 Zugriff auf Eigenschaften in der Spezifikation

Der Operator zum Holen und Setzen von Eigenschaften verwendet die interne Operation ToPropertyKey(), die wie folgt funktioniert:

7.7 Übergreifende Realms mit Symbolen

Ein *Code-Realm* (kurz: Realm) ist ein Kontext, in dem Code-Teile existieren. Er umfasst globale Variablen, geladene Module und mehr. Auch wenn Code „innerhalb“ genau eines Realms existiert, kann er Zugriff auf Code in anderen Realms haben. Zum Beispiel hat jedes Frame in einem Browser seinen eigenen Realm. Und die Ausführung kann von einem Frame zum anderen springen, wie das folgende HTML zeigt.

<head>
    <script>
        function test(arr) {
            var iframe = frames[0];
            // This code and the iframe’s code exist in
            // different realms. Therefore, global variables
            // such as Array are different:
            console.log(Array === iframe.Array); // false
            console.log(arr instanceof Array); // false
            console.log(arr instanceof iframe.Array); // true

            // But: symbols are the same
            console.log(Symbol.iterator ===
                        iframe.Symbol.iterator); // true
        }
    </script>
</head>
<body>
    <iframe srcdoc="<script>window.parent.test([])</script>">
</iframe>
</body>

Das Problem ist, dass jeder Realm seine eigenen globalen Variablen hat, wobei jede Variable Array auf ein anderes Objekt verweist, obwohl sie alle im Wesentlichen dasselbe Objekt sind. Ebenso werden Bibliotheken und Benutzercode pro Realm geladen und jeder Realm hat eine andere Version desselben Objekts.

Objekte werden nach Identität verglichen, aber Booleans, Zahlen und Strings werden nach Wert verglichen. Daher ist eine Zahl 123, unabhängig davon, in welchem Realm sie entstanden ist, von allen anderen 123ern nicht zu unterscheiden. Das ist ähnlich wie bei dem Zahlenliteral 123, das immer denselben Wert ergibt.

Symbole haben individuelle Identitäten und reisen daher nicht so reibungslos über Realms wie andere primitive Werte. Das ist ein Problem für Symbole wie Symbol.iterator, die über Realms hinweg funktionieren sollten: Wenn ein Objekt in einem Realm iterierbar ist, sollte es in allen Realms iterierbar sein. Alle eingebauten Symbole werden von der JavaScript-Engine verwaltet, die sicherstellt, dass z. B. Symbol.iterator in jedem Realm derselbe Wert ist. Wenn eine Bibliothek realm-übergreifende Symbole bereitstellen möchte, muss sie sich auf zusätzliche Unterstützung verlassen, die in Form der *globalen Symbol-Registrierung* erfolgt: Diese Registrierung ist für alle Realms global und ordnet Strings Symbolen zu. Für jedes Symbol muss sich die Bibliothek einen möglichst eindeutigen String ausdenken. Um das Symbol zu erstellen, verwendet sie nicht Symbol(), sondern fragt die Registrierung nach dem Symbol, dem der String zugeordnet ist. Wenn die Registrierung bereits einen Eintrag für den String hat, wird das zugehörige Symbol zurückgegeben. Andernfalls werden zuerst Eintrag und Symbol erstellt.

Sie fragen die Registrierung über Symbol.for() nach einem Symbol und rufen den String, der einem Symbol zugeordnet ist (sein *Schlüssel*), über Symbol.keyFor() ab.

> const sym = Symbol.for('Hello everybody!');
> Symbol.keyFor(sym)
'Hello everybody!'

Realm-übergreifende Symbole, wie Symbol.iterator, die von der JavaScript-Engine bereitgestellt werden, befinden sich nicht in der Registrierung.

> Symbol.keyFor(Symbol.iterator)
undefined

7.8 FAQ: Symbole

7.8.1 Kann ich Symbole zur Definition privater Eigenschaften verwenden?

Der ursprüngliche Plan war, dass Symbole private Eigenschaften unterstützen (es hätte öffentliche und private Symbole gegeben). Aber dieses Feature wurde gestrichen, da die Verwendung von „get“ und „set“ (zwei Meta-Objekt-Protokoll-Operationen) zur Verwaltung privater Daten nicht gut mit Proxies interagiert.

Diese beiden Ziele stehen im Widerspruch zueinander. Das Kapitel über Klassen erklärt Ihre Optionen zur Verwaltung privater Daten. Symbole sind eine dieser Optionen, aber Sie erhalten nicht dieselbe Sicherheit, die Sie von privaten Symbolen erhalten würden, da es möglich ist, die Symbole, die als Eigenschaftsschlüssel eines Objekts verwendet werden, über Object.getOwnPropertySymbols() und Reflect.ownKeys() zu ermitteln.

7.8.2 Sind Symbole primitive Typen oder Objekte?

In mancher Hinsicht sind Symbole wie primitive Werte, in anderer Hinsicht wie Objekte.

Was sind Symbole also – primitive Werte oder Objekte? Letztendlich wurden sie zu Primitiven gemacht, und zwar aus zwei Gründen.

Erstens sind Symbole eher wie Strings als wie Objekte: Sie sind ein grundlegender Wert der Sprache, sie sind unveränderlich und sie können als Eigenschaftsschlüssel verwendet werden. Dass Symbole eindeutige Identitäten haben, widerspricht nicht unbedingt ihrer Ähnlichkeit mit Strings: UUID-Algorithmen erzeugen Strings, die quasi-eindeutig sind.

Zweitens werden Symbole am häufigsten als Eigenschaftsschlüssel verwendet, daher ist es sinnvoll, die JavaScript-Spezifikation und Implementierungen für diesen Anwendungsfall zu optimieren. Dann benötigen Symbole nicht viele Fähigkeiten von Objekten.

Dass Symbole diese Fähigkeiten nicht haben, erleichtert die Spezifikation und die Implementierungen. Das V8-Team hat auch gesagt, dass es bei Eigenschaftsschlüsseln einfacher ist, einen primitiven Typ zu einem Sonderfall zu machen als bestimmte Objekte.

7.8.3 Brauchen wir wirklich Symbole? Reichen Strings nicht aus?

Im Gegensatz zu Strings sind Symbole eindeutig und verhindern Namenskonflikte. Das ist für Tokens wie Farben nett zu haben, aber unerlässlich für die Unterstützung von Metadaten-Methoden wie derjenigen, deren Schlüssel Symbol.iterator ist. Python verwendet den speziellen Namen __iter__, um Konflikte zu vermeiden. Sie können Doppelunterstrich-Namen für Mechanismen der Programmiersprache reservieren, aber was soll eine Bibliothek tun? Mit Symbolen haben wir einen Erweiterungsmechanismus, der für alle funktioniert. Wie Sie später im Abschnitt über öffentliche Symbole sehen werden, nutzt JavaScript selbst diesen Mechanismus bereits ausgiebig.

Es gibt eine hypothetische Alternative zu Symbolen, wenn es um kollisionsfreie Eigenschaftsschlüssel geht: die Verwendung einer Namenskonvention. Zum Beispiel Strings mit URLs (z. B. 'http://example.com/iterator'). Aber das würde eine zweite Kategorie von Eigenschaftsschlüsseln einführen (im Gegensatz zu „normalen“ Eigenschaftsnamen, die normalerweise gültige Bezeichner sind und keine Doppelpunkte, Schrägstriche, Punkte usw. enthalten), was im Grunde das ist, was Symbole sowieso sind. Dann könnten wir genauso gut eine neue Art von Wert einführen.

7.8.4 Sind JavaScript-Symbole wie Rubys Symbole?

Nein, das sind sie nicht.

Rubys Symbole sind im Grunde Literale zur Erstellung von Werten. Wenn dasselbe Symbol zweimal erwähnt wird, werden zweimal dieselben Werte erzeugt.

:foo == :foo

Die JavaScript-Funktion Symbol() ist eine Factory für Symbole – jeder zurückgegebene Wert ist eindeutig.

Symbol('foo') !== Symbol('foo')

7.9 Die Schreibweise von bekannten Symbolen: Warum Symbol.iterator und nicht Symbol.ITERATOR (usw.)?

Bekannte Symbole werden in Eigenschaften gespeichert, deren Namen mit Kleinbuchstaben beginnen und camel-cased sind. In gewisser Weise sind diese Eigenschaften Konstanten, und es ist üblich, dass Konstanten Namen in Großbuchstaben haben (Math.PI usw.). Aber die Begründung für ihre Schreibweise ist anders: Bekannte Symbole werden anstelle von normalen Eigenschaftsschlüsseln verwendet, weshalb ihre „Namen“ den Regeln für Eigenschaftsschlüssel folgen, nicht den Regeln für Konstanten.

7.10 Die Symbol-API

Dieser Abschnitt gibt einen Überblick über die ECMAScript 6 API für Symbole.

7.10.1 Die Funktion Symbol

Symbol(beschreibung?) : symbol
Erstellt ein neues Symbol. Der optionale Parameter beschreibung ermöglicht es Ihnen, dem Symbol eine Beschreibung zu geben. Die einzige Möglichkeit, die Beschreibung abzurufen, ist die Umwandlung des Symbols in einen String (über toString() oder String()). Das Ergebnis einer solchen Umwandlung ist 'Symbol('+beschreibung+')'.

> const sym = Symbol('hello');
> String(sym)
'Symbol(hello)'

Symbol kann nicht als Konstruktor verwendet werden – eine Ausnahme wird ausgelöst, wenn Sie es über new aufrufen.

7.10.2 Methoden von Symbolen

Die einzige nützliche Methode, die Symbole haben, ist toString() (über Symbol.prototype.toString()).

7.10.3 Symbole in andere Werte konvertieren

Umwandlung in Explizite Konvertierung Konvertierung (implizite Umwandlung)
boolean Boolean(sym) → OK !sym → OK
number Number(sym)TypeError sym*2TypeError
string String(sym) → OK ''+symTypeError
  sym.toString() → OK `${sym}`TypeError
object Object(sym) → OK Object.keys(sym) → OK

7.10.4 Bekannte Symbole

Das globale Objekt Symbol hat mehrere Eigenschaften, die als Konstanten für sogenannte *bekannte Symbole* dienen. Diese Symbole ermöglichen es Ihnen, zu konfigurieren, wie ES6 ein Objekt behandelt, indem Sie sie als Eigenschaftsschlüssel verwenden. Dies ist eine Liste von allen bekannten Symbolen.

7.10.5 Globale Symbol-Registrierung

Wenn Sie möchten, dass ein Symbol in allen Realms dasselbe ist, müssen Sie die globale Symbol-Registrierung über die folgenden beiden Methoden verwenden.

Weiter: 8. Template-Literale