Symbole sind primitive Werte, die über die Factory-Funktion Symbol() erzeugt werden.
const mySymbol = Symbol('mySymbol');Der Parameter ist optional und liefert eine Beschreibung, die hauptsächlich für das Debugging nützlich ist.
Symbole sind primitive Werte.
Sie müssen über typeof kategorisiert werden.
const sym = Symbol();
assert.equal(typeof sym, 'symbol');Sie können Schlüsseln für Eigenschaften in Objekten sein.
const obj = {
[sym]: 123,
};Obwohl Symbole primitive Werte sind, sind sie auch Objekt-ähnlich, insofern als jeder durch Symbol() erzeugte Wert eindeutig ist und nicht nach Wert verglichen wird.
> Symbol() === Symbol()
falseVor Symbolen waren Objekte die beste Wahl, wenn wir Werte benötigten, die eindeutig waren (nur mit sich selbst gleich).
const string1 = 'abc';
const string2 = 'abc';
assert.equal(
string1 === string2, true); // not unique
const object1 = {};
const object2 = {};
assert.equal(
object1 === object2, false); // unique
const symbol1 = Symbol();
const symbol2 = Symbol();
assert.equal(
symbol1 === symbol2, false); // uniqueDer Parameter, den wir der Symbol-Factory-Funktion übergeben, liefert eine Beschreibung für das erzeugte Symbol.
const mySymbol = Symbol('mySymbol');Die Beschreibung kann auf zwei Arten abgerufen werden.
Erstens ist sie Teil des Strings, der von .toString() zurückgegeben wird.
assert.equal(mySymbol.toString(), 'Symbol(mySymbol)');Zweitens können wir seit ES2019 die Beschreibung über die Eigenschaft .description abrufen.
assert.equal(mySymbol.description, 'mySymbol');Die Hauptanwendungsfälle für Symbole sind:
Angenommen, Sie möchten Konstanten erstellen, die die Farben Rot, Orange, Gelb, Grün, Blau und Violett darstellen. Eine einfache Möglichkeit, dies zu tun, wäre die Verwendung von Strings.
const COLOR_BLUE = 'Blue';Auf der positiven Seite erzeugt das Ausgeben dieser Konstante eine hilfreiche Ausgabe. Auf der negativen Seite besteht die Gefahr, einen nicht verwandten Wert mit einer Farbe zu verwechseln, da zwei Strings mit demselben Inhalt als gleich betrachtet werden.
const MOOD_BLUE = 'Blue';
assert.equal(COLOR_BLUE, MOOD_BLUE);Dieses Problem können wir mit Symbolen lösen.
const COLOR_BLUE = Symbol('Blue');
const MOOD_BLUE = Symbol('Blue');
assert.notEqual(COLOR_BLUE, MOOD_BLUE);Verwenden wir symbol-basierte Konstanten, um eine Funktion zu implementieren.
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);
}
}
assert.equal(getComplement(COLOR_YELLOW), COLOR_VIOLET);Die Schlüsseln von Eigenschaften (Feldern) in Objekten werden auf zwei Ebenen verwendet.
Das Programm arbeitet auf einer Basisebene. Die Schlüsseln auf dieser Ebene spiegeln die Problemdomäne wider – den Bereich, in dem ein Programm ein Problem löst – zum Beispiel:
ECMAScript und viele Bibliotheken arbeiten auf einer Metaebene. Sie verwalten Daten und stellen Dienste bereit, die nicht Teil der Problemdomäne sind – zum Beispiel:
Die Standardmethode .toString() wird von ECMAScript verwendet, wenn eine String-Repräsentation eines Objekts erstellt wird (Zeile A).
const point = {
x: 7,
y: 4,
toString() {
return `(${this.x}, ${this.y})`;
},
};
assert.equal(
String(point), '(7, 4)'); // (A).x und .y sind Basisebenen-Eigenschaften – sie werden verwendet, um das Problem der Berechnung mit Punkten zu lösen. .toString() ist eine Metaebenen-Eigenschaft – sie hat nichts mit der Problemdomäne zu tun.
Die Standard-ECMAScript-Methode .toJSON().
const point = {
x: 7,
y: 4,
toJSON() {
return [this.x, this.y];
},
};
assert.equal(
JSON.stringify(point), '[7,4]');.x und .y sind Basisebenen-Eigenschaften, .toJSON() ist eine Metaebenen-Eigenschaft.
Die Basis- und die Metaebene eines Programms müssen unabhängig sein: Basisebenen-Eigenschaftsschlüsseln sollten nicht mit Metaebenen-Eigenschaftsschlüsseln kollidieren.
Wenn wir Namen (Strings) als Eigenschaftsschlüsseln verwenden, stehen wir vor zwei Herausforderungen:
Wenn eine Sprache zum ersten Mal erstellt wird, kann sie beliebige Metaebenen-Namen verwenden. Der Basisebenen-Code muss diese Namen vermeiden. Später jedoch, wenn bereits viel Basisebenen-Code existiert, können Metaebenen-Namen nicht mehr frei gewählt werden.
Wir könnten Namensregeln einführen, um Basis- und Metaebene zu trennen. Zum Beispiel umschließt Python Metaebenen-Namen mit zwei Unterstrichen: __init__, __iter__, __hash__ usw. Jedoch existieren die Metaebenen-Namen der Sprache und die Metaebenen-Namen der Bibliotheken immer noch im selben Namensraum und können kollidieren.
Hier sind zwei Beispiele, wo letzteres für JavaScript ein Problem war:
Im Mai 2018 musste die Array-Methode .flatten() in .flat() umbenannt werden, da der frühere Name bereits von Bibliotheken verwendet wurde (Quelle).
Im November 2020 musste die Array-Methode .item() in .at() umbenannt werden, da der frühere Name bereits von einer Bibliothek verwendet wurde (Quelle).
Symbole, die als Eigenschaftsschlüsseln verwendet werden, helfen uns hier: Jedes Symbol ist eindeutig und ein Symbol-Schlüssel kollidiert niemals mit einem anderen String- oder Symbol-Schlüssel.
Als Beispiel nehmen wir an, wir schreiben eine Bibliothek, die Objekte unterschiedlich behandelt, wenn sie eine spezielle Methode implementieren. So sieht die Definition eines Eigenschaftsschlüssels für eine solche Methode und deren Implementierung für ein Objekt aus:
const specialMethod = Symbol('specialMethod');
const obj = {
_id: 'kf12oi',
[specialMethod]() { // (A)
return this._id;
}
};
assert.equal(obj[specialMethod](), 'kf12oi');Die eckigen Klammern in Zeile A ermöglichen es uns, anzugeben, dass die Methode den Schlüssel specialMethod haben muss. Weitere Details werden in §28.7.2 „Berechnete Schlüssel in Objekt-Literalen“ erläutert.
Symbole, die innerhalb von ECMAScript spezielle Rollen spielen, werden als öffentlich bekannte Symbole bezeichnet. Beispiele hierfür sind:
Symbol.iterator: macht ein Objekt iterierbar. Es ist der Schlüssel einer Methode, die einen Iterator zurückgibt. Weitere Informationen zu diesem Thema finden Sie in §30 „Synchrone Iteration“.
Symbol.hasInstance: passt an, wie instanceof funktioniert. Wenn ein Objekt eine Methode mit diesem Schlüssel implementiert, kann es auf der rechten Seite dieses Operators verwendet werden. Zum Beispiel:
const PrimitiveNull = {
[Symbol.hasInstance](x) {
return x === null;
}
};
assert.equal(null instanceof PrimitiveNull, true);Symbol.toStringTag: beeinflusst die Standardmethode .toString().
> String({})
'[object Object]'
> String({ [Symbol.toStringTag]: 'is no money' })
'[object is no money]'Hinweis: Es ist normalerweise besser, .toString() zu überschreiben.
Übungen: Öffentlich bekannte Symbole
Symbol.toStringTag: exercises/symbols/to_string_tag_test.mjsSymbol.hasInstance: exercises/symbols/has_instance_test.mjsWas passiert, wenn wir ein Symbol sym in einen anderen primitiven Typ konvertieren? Tab. 15 gibt die Antworten.
| Konvertieren nach | Explizite Konvertierung | Koerzition (implizite Konv.) |
|---|---|---|
| boolean | Boolean(sym) → OK |
!sym → OK |
| number | Number(sym) → TypeError |
sym*2 → TypeError |
| string | String(sym) → OK |
''+sym → TypeError |
sym.toString() → OK |
`${sym}` → TypeError |
Eine wichtige Fallstricke bei Symbolen ist, wie oft Ausnahmen geworfen werden, wenn sie in etwas anderes konvertiert werden. Was steckt dahinter? Erstens ist die Konvertierung in eine Zahl nie sinnvoll und sollte gewarnt werden. Zweitens ist die Konvertierung eines Symbols in einen String tatsächlich für die Diagnoseausgabe nützlich. Es ist jedoch auch sinnvoll, vor einer versehentlichen Umwandlung eines Symbols in einen String zu warnen (was eine andere Art von Eigenschaftsschlüssel ist).
const obj = {};
const sym = Symbol();
assert.throws(
() => { obj['__'+sym+'__'] = true },
{ message: 'Cannot convert a Symbol value to a string' });Der Nachteil ist, dass die Ausnahmen die Arbeit mit Symbolen komplizierter machen. Sie müssen Symbole explizit konvertieren, wenn Sie Strings über den Plus-Operator zusammenfügen.
> const mySymbol = Symbol('mySymbol');
> 'Symbol I used: ' + mySymbol
TypeError: Cannot convert a Symbol value to a string
> 'Symbol I used: ' + String(mySymbol)
'Symbol I used: Symbol(mySymbol)' Quiz
Siehe Quiz-App.