[ ] und gewrappte SchlüsselSymbol.iterator und nicht Symbol.ITERATOR (usw.)?SymbolSymbole 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)
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.
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.
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:
Reflect.ownKeys()[]Object.assign()Die folgenden Operationen ignorieren Symbole als Eigenschaftsschlüssel:
Object.keys()Object.getOwnPropertyNames()for-in-SchleifeECMAScript 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'
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
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.
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.
Die Möglichkeit, Eigenschaften zu erstellen, deren Schlüssel niemals mit anderen Schlüsseln kollidieren, ist in zwei Situationen nützlich:
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.
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
Falls Sie denken, dass Namenskonflikte keine Rolle spielen, hier sind drei Beispiele, wo Namenskonflikte bei der Entwicklung der JavaScript-Standardbibliothek Probleme verursacht haben:
Array.prototype.values() erstellt wurde, brach sie bestehenden Code, bei dem with mit einem Array verwendet wurde und eine Variable values in einem äußeren Geltungsbereich überschattete (Bug-Report 1, Bug-Report 2). Daher wurde ein Mechanismus eingeführt, um Eigenschaften vor with zu verbergen (Symbol.unscopables).String.prototype.contains kollidierte mit einer von MooTools hinzugefügten Methode und musste in String.prototype.includes umbenannt werden (Bug-Report).Array.prototype.contains kollidierte ebenfalls mit einer von MooTools hinzugefügten Methode und musste in Array.prototype.includes umbenannt werden (Bug-Report).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.
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*2 → TypeError |
| string |
String(sym) → OK |
''+sym → TypeError |
sym.toString() → OK |
`${sym}` → TypeError |
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
Konvertierung (implizite Umwandlung) ist für Symbole oft verboten. Dieser Abschnitt erklärt, warum.
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;
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.
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.
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
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
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
+) Der Additionsoperator funktioniert wie folgt:
ToString()), verketten Sie sie und geben Sie das Ergebnis zurück.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
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
[ ] 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'
Der Operator zum Holen und Setzen von Eigenschaften verwendet die interne Operation ToPropertyKey(), die wie folgt funktioniert:
ToPrimitive() mit dem bevorzugten Typ String in einen primitiven Typ um.[@@toPrimitive]() hat, wird diese Methode verwendet, um es in einen primitiven Wert umzuwandeln. Symbole haben eine solche Methode, die das gewrappte Symbol zurückgibt.toString() in einen primitiven Wert umgewandelt – wenn dieser einen primitiven Wert zurückgibt. Andernfalls wird valueOf() verwendet – wenn dieser einen primitiven Wert zurückgibt. Andernfalls wird ein TypeError ausgelöst. Der bevorzugte Typ String bestimmt, dass toString() zuerst und valueOf() danach aufgerufen wird.ToString() in einen String.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
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.
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.
instanceof, Object.keys() usw.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.
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.
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')
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.
Dieser Abschnitt gibt einen Überblick über die ECMAScript 6 API für Symbole.
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.
Die einzige nützliche Methode, die Symbole haben, ist toString() (über Symbol.prototype.toString()).
| Umwandlung in | Explizite Konvertierung | Konvertierung (implizite Umwandlung) |
|---|---|---|
| boolean |
Boolean(sym) → OK |
!sym → OK |
| number |
Number(sym) → TypeError |
sym*2 → TypeError |
| string |
String(sym) → OK |
''+sym → TypeError |
sym.toString() → OK |
`${sym}` → TypeError |
|
| object |
Object(sym) → OK |
Object.keys(sym) → OK |
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.
Symbol.hasInstance (Methode)C, das Verhalten von x instanceof C anzupassen.Symbol.toPrimitive (Methode)Symbol.toStringTag (String)Object.prototype.toString() aufgerufen, um die Standard-String-Beschreibung eines Objekts obj zu berechnen. '[object ' + obj[Symbol.toStringTag] + ']'
Symbol.unscopables (Objekt)with-Anweisung auszublenden.Symbol.iterator (Methode)for-of-Schleife und dem Spread-Operator (...) iteriert werden). Die Methode gibt einen *Iterator* zurück. Details: Kapitel „Iterables und Iteratoren“.String.prototype.match(x, ···) wird an x[Symbol.match](···) delegiert.String.prototype.replace(x, ···) wird an x[Symbol.replace](···) delegiert.String.prototype.search(x, ···) wird an x[Symbol.search](···) delegiert.String.prototype.split(x, ···) wird an x[Symbol.split](···) delegiert.Die Details werden in Abs. „String-Methoden, die reguläre Ausdrucksarbeiten an ihre Parameter delegieren“ im Kapitel über Strings erklärt.
Symbol.species (Methode)Array.prototype.map()) Objekte erstellen, die this ähneln. Die Details werden in Kapitel über Klassen erklärt.Symbol.isConcatSpreadable (Boolean)Array.prototype.concat() die indizierten Elemente eines Objekts zu seinem Ergebnis hinzufügt („spreadet“) oder das Objekt als einzelnes Element (Details werden im Kapitel über Arrays erklärt).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.
Symbol.for(str) : symbolstr in der Registrierung ist. Wenn str noch nicht in der Registrierung ist, wird ein neues Symbol erstellt und unter dem Schlüssel str in der Registrierung abgelegt.Symbol.keyFor(sym) : stringsym in der Registrierung zugeordnet ist. Wenn sym nicht in der Registrierung ist, gibt diese Methode undefined zurück. Diese Methode kann verwendet werden, um Symbole zu serialisieren (z. B. nach JSON).