Laut Spezifikation dürfen nur zwei primitive Typen als Objekteigenschaftsschlüssel dienen:
String-Typ, oder
Symboltyp.
Wenn andernfalls ein anderer Typ verwendet wird, beispielsweise eine Zahl, wird dieser automatisch in eine Zeichenfolge konvertiert. obj[1]
ist also dasselbe wie obj["1"]
und obj[true]
ist dasselbe wie obj["true"]
.
Bisher haben wir nur Strings verwendet.
Lassen Sie uns nun die Symbole erkunden und sehen, was sie für uns tun können.
Ein „Symbol“ stellt eine eindeutige Kennung dar.
Ein Wert dieses Typs kann mit Symbol()
erstellt werden:
let id = Symbol();
Bei der Erstellung können wir Symbolen eine Beschreibung (auch Symbolname genannt) geben, die vor allem für Debugging-Zwecke nützlich ist:
// id ist ein Symbol mit der Beschreibung „id“ let id = Symbol("id");
Die Einzigartigkeit der Symbole ist garantiert. Selbst wenn wir viele Symbole mit genau derselben Beschreibung erstellen, handelt es sich um unterschiedliche Werte. Die Beschreibung ist nur eine Bezeichnung, die keinerlei Auswirkungen hat.
Hier sind zum Beispiel zwei Symbole mit der gleichen Beschreibung – sie sind nicht gleich:
let id1 = Symbol("id"); let id2 = Symbol("id"); alarm(id1 == id2); // FALSCH
Wenn Sie mit Ruby oder einer anderen Sprache vertraut sind, die ebenfalls über „Symbole“ verfügt, lassen Sie sich bitte nicht täuschen. JavaScript-Symbole sind unterschiedlich.
Zusammenfassend lässt sich sagen, dass ein Symbol ein „primitiver einzigartiger Wert“ mit einer optionalen Beschreibung ist. Mal sehen, wo wir sie einsetzen können.
Symbole werden nicht automatisch in eine Zeichenfolge konvertiert
Die meisten Werte in JavaScript unterstützen die implizite Konvertierung in einen String. Wir können zum Beispiel fast jeden Wert alert
, und es wird funktionieren. Symbole sind etwas Besonderes. Sie führen keine automatische Konvertierung durch.
Diese alert
zeigt beispielsweise einen Fehler an:
let id = Symbol("id"); alarm(id); // TypeError: Ein Symbolwert kann nicht in einen String konvertiert werden
Das ist ein „Sprachschutz“ gegen Missverständnisse, da Zeichenfolgen und Symbole grundsätzlich unterschiedlich sind und nicht versehentlich ineinander umgewandelt werden sollten.
Wenn wir wirklich ein Symbol anzeigen möchten, müssen wir .toString()
explizit dafür aufrufen, wie hier:
let id = Symbol("id"); alarm(id.toString()); // Symbol(id), jetzt funktioniert es
Oder rufen Sie die Eigenschaft symbol.description
ab, um nur die Beschreibung anzuzeigen:
let id = Symbol("id"); Alert(id.description); // Ausweis
Mit Symbolen können wir „versteckte“ Eigenschaften eines Objekts erstellen, auf die kein anderer Teil des Codes versehentlich zugreifen oder diese überschreiben kann.
Wenn wir beispielsweise mit user
arbeiten, die zu Code eines Drittanbieters gehören. Wir möchten ihnen gerne Kennungen hinzufügen.
Verwenden wir dafür einen Symbolschlüssel:
let user = { // gehört zu einem anderen Code Name: „John“ }; let id = Symbol("id"); Benutzer[id] = 1; alarm( user[id] ); // Wir können auf die Daten zugreifen, indem wir das Symbol als Schlüssel verwenden
Welchen Vorteil hat die Verwendung von Symbol("id")
gegenüber einer Zeichenfolge "id"
?
Da user
zu einer anderen Codebasis gehören, ist es unsicher, ihnen Felder hinzuzufügen, da wir möglicherweise das vordefinierte Verhalten in dieser anderen Codebasis beeinflussen. Auf Symbole kann jedoch nicht versehentlich zugegriffen werden. Der Drittanbietercode erkennt neu definierte Symbole nicht, daher ist es sicher, Symbole zu den user
hinzuzufügen.
Stellen Sie sich außerdem vor, dass ein anderes Skript für eigene Zwecke eine eigene Kennung innerhalb user
haben möchte.
Dann kann dieses Skript sein eigenes Symbol("id")
erstellen, etwa so:
// ... let id = Symbol("id"); user[id] = „Ihr ID-Wert“;
Es wird keinen Konflikt zwischen unseren und ihren Bezeichnern geben, da Symbole immer unterschiedlich sind, auch wenn sie denselben Namen haben.
…Aber wenn wir für denselben Zweck eine Zeichenfolge "id"
anstelle eines Symbols verwenden würden, gäbe es einen Konflikt:
let user = { name: "John" }; // Unser Skript verwendet die Eigenschaft „id“. user.id = „Unser ID-Wert“; // ...Ein anderes Skript benötigt ebenfalls „id“ für seine Zwecke... user.id = „Ihr ID-Wert“ // Boom! von einem anderen Skript überschrieben!
Wenn wir ein Symbol in einem Objektliteral {...}
verwenden möchten, müssen wir es in eckige Klammern setzen.
So was:
let id = Symbol("id"); let user = { Name: „John“, [id]: 123 // nicht „id“: 123 };
Das liegt daran, dass wir als Schlüssel den Wert der Variablen id
benötigen, nicht den String „id“.
Symbolische Eigenschaften nehmen nicht an for..in
Schleife teil.
Zum Beispiel:
let id = Symbol("id"); let user = { Name: „John“, Alter: 30, [id]: 123 }; for (Benutzer eingeben lassen) alarm(key); // Name, Alter (keine Symbole) // der direkte Zugriff über das Symbol funktioniert alarm( "Direct: " + user[id] ); // Direkt: 123
Object.keys(user) ignoriert sie ebenfalls. Das ist Teil des allgemeinen Prinzips „Symbolische Eigenschaften verbergen“. Wenn ein anderes Skript oder eine Bibliothek unser Objekt durchläuft, greift es nicht unerwartet auf eine symbolische Eigenschaft zu.
Im Gegensatz dazu kopiert Object.assign sowohl String- als auch Symboleigenschaften:
let id = Symbol("id"); let user = { [id]: 123 }; let clone = Object.assign({}, user); alarm( clone[id] ); // 123
Hier gibt es kein Paradoxon. Das ist beabsichtigt. Die Idee ist, dass wir beim Klonen eines Objekts oder beim Zusammenführen von Objekten normalerweise möchten, dass alle Eigenschaften kopiert werden (einschließlich Symbole wie id
).
Wie wir gesehen haben, sind normalerweise alle Symbole unterschiedlich, auch wenn sie den gleichen Namen haben. Aber manchmal möchten wir, dass gleichnamige Symbole dieselben Entitäten sind. Beispielsweise möchten verschiedene Teile unserer Anwendung auf das Symbol "id"
zugreifen, das genau dieselbe Eigenschaft bedeutet.
Um dies zu erreichen, gibt es ein globales Symbolregister . Wir können darin Symbole erstellen und später darauf zugreifen, und es garantiert, dass wiederholte Zugriffe mit demselben Namen genau dasselbe Symbol zurückgeben.
Um ein Symbol aus der Registrierung zu lesen (bei Abwesenheit zu erstellen), verwenden Sie Symbol.for(key)
.
Dieser Aufruf überprüft die globale Registrierung. Wenn dort ein als key
beschriebenes Symbol vorhanden ist, wird es zurückgegeben. Andernfalls wird ein neues Symbol Symbol(key)
erstellt und unter dem angegebenen key
in der Registrierung gespeichert.
Zum Beispiel:
// Aus der globalen Registrierung lesen let id = Symbol.for("id"); // wenn das Symbol nicht existierte, wird es erstellt // noch einmal lesen (vielleicht aus einem anderen Teil des Codes) let idAgain = Symbol.for("id"); // das gleiche Symbol alarm( id === idAgain ); // WAHR
Symbole innerhalb der Registrierung werden als globale Symbole bezeichnet. Wenn wir ein anwendungsweites Symbol wollen, das überall im Code zugänglich ist, dann sind sie dafür da.
Das klingt nach Ruby
In einigen Programmiersprachen wie Ruby gibt es ein einzelnes Symbol pro Name.
Wie wir sehen können, gilt dies in JavaScript für globale Symbole.
Wir haben gesehen, dass Symbol.for(key)
für globale Symbole ein Symbol nach Namen zurückgibt. Um das Gegenteil zu tun – einen Namen durch globales Symbol zurückzugeben – können wir Folgendes verwenden: Symbol.keyFor(sym)
:
Zum Beispiel:
// Symbol nach Namen abrufen let sym = Symbol.for("name"); let sym2 = Symbol.for("id"); // Namen nach Symbol abrufen alarm( Symbol.keyFor(sym) ); // Name alarm( Symbol.keyFor(sym2) ); // Ausweis
Symbol.keyFor
verwendet intern die globale Symbolregistrierung, um den Schlüssel für das Symbol zu suchen. Es funktioniert also nicht für nicht globale Symbole. Wenn das Symbol nicht global ist, kann es nicht gefunden werden und gibt undefined
zurück.
Allerdings verfügen alle Symbole über die description
.
Zum Beispiel:
let globalSymbol = Symbol.for("name"); let localSymbol = Symbol("name"); alarm( Symbol.keyFor(globalSymbol) ); // Name, globales Symbol alarm( Symbol.keyFor(localSymbol) ); // undefiniert, nicht global alarm( localSymbol.description ); // Name
Es gibt viele „Systemsymbole“, die JavaScript intern verwendet, und wir können sie zur Feinabstimmung verschiedener Aspekte unserer Objekte verwenden.
Sie sind in der Spezifikation in der Tabelle „Bekannte Symbole“ aufgeführt:
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
…und so weiter.
Symbol.toPrimitive
können wir beispielsweise die Konvertierung von Objekten in Primitive beschreiben. Wir werden seine Verwendung sehr bald sehen.
Auch andere Symbole werden uns bekannt, wenn wir die entsprechenden Sprachmerkmale studieren.
Symbol
ist ein primitiver Typ für eindeutige Bezeichner.
Symbole werden mit Symbol()
-Aufruf mit einer optionalen Beschreibung (Name) erstellt.
Symbole haben immer unterschiedliche Werte, auch wenn sie den gleichen Namen haben. Wenn wir möchten, dass gleichnamige Symbole gleich sind, sollten wir die globale Registrierung verwenden: Symbol.for(key)
gibt ein globales Symbol mit key
als Namen zurück (erstellt es bei Bedarf). Mehrere Aufrufe von Symbol.for
mit demselben key
geben genau dasselbe Symbol zurück.
Symbole haben zwei Hauptanwendungsfälle:
„Versteckte“ Objekteigenschaften.
Wenn wir einem Objekt, das zu einem anderen Skript oder einer Bibliothek „gehört“, eine Eigenschaft hinzufügen möchten, können wir ein Symbol erstellen und es als Eigenschaftsschlüssel verwenden. Eine symbolische Eigenschaft erscheint nicht in for..in
, sodass sie nicht versehentlich zusammen mit anderen Eigenschaften verarbeitet wird. Außerdem wird nicht direkt darauf zugegriffen, da ein anderes Skript nicht über unser Symbol verfügt. So wird das Eigentum vor versehentlicher Nutzung oder Überschreibung geschützt.
So können wir mithilfe symbolischer Eigenschaften „heimlich“ etwas in Objekten verbergen, die wir benötigen, andere aber nicht sehen sollten.
Es gibt viele von JavaScript verwendete Systemsymbole, auf die als Symbol.*
zugegriffen werden kann. Wir können sie verwenden, um einige eingebaute Verhaltensweisen zu ändern. Später im Tutorial verwenden wir beispielsweise Symbol.iterator
für Iterables, Symbol.toPrimitive
zum Einrichten der Objekt-zu-Primitiv-Konvertierung usw.
Technisch gesehen sind Symbole nicht zu 100 % verborgen. Es gibt eine integrierte Methode Object.getOwnPropertySymbols(obj), mit der wir alle Symbole abrufen können. Außerdem gibt es eine Methode namens Reflect.ownKeys(obj), die alle Schlüssel eines Objekts zurückgibt, einschließlich symbolischer Schlüssel. Die meisten Bibliotheken, integrierten Funktionen und Syntaxkonstrukte verwenden diese Methoden jedoch nicht.