Zeichenprimitive sind eine der Innovationen des ES6-Standards, die JavaScript einige wertvolle Funktionen verliehen haben. Symbole, die durch den Symboldatentyp dargestellt werden, sind besonders nützlich, wenn sie als Bezeichner für Objekteigenschaften verwendet werden. Im Zusammenhang mit einem solchen Szenario ihrer Anwendung stellt sich die Frage, was sie können, was die Linien nicht können.

In dem Material, dessen Übersetzung wir heute veröffentlichen, werden wir über den Symboldatentyp in JavaScript sprechen. Wir beginnen mit der Überprüfung einiger JavaScript-Funktionen, die Sie zum Umgang mit Symbolen benötigen.
Vorläufige Informationen
In JavaScript gibt es tatsächlich zwei Arten von Werten. Die ersten typprimitiven Werte, das zweite Objekt (sie enthalten auch Funktionen). Zu den primitiven Werten gehören einfache Datentypen wie Zahlen (dies umfasst alles von Ganzzahlen bis zu Gleitkommazahlen,
Infinity
und
NaN
Werten), logische Werte, Zeichenfolgen,
undefined
und
null
. Beachten Sie, dass
null
ein primitiver Wert ist, während die Überprüfung von
typeof null === 'object'
true
ergibt.
Primitive Werte sind unveränderlich. Sie können nicht geändert werden. Natürlich können Sie etwas Neues in eine Variable schreiben, in der ein primitiver Wert gespeichert ist. Dies schreibt beispielsweise einen neuen Wert in die Variable
x
:
let x = 1; x++;
Gleichzeitig gibt es jedoch keine Änderung (Mutation) des primitiven numerischen Wertes
1
.
In einigen Sprachen, zum Beispiel in C, gibt es Konzepte, Argumente von Funktionen als Referenz und als Wert zu übergeben. JavaScript hat auch etwas ähnliches. Wie genau die Arbeit mit Daten organisiert ist, hängt von ihrem Typ ab. Wenn ein primitiver Wert, der durch eine bestimmte Variable dargestellt wird, an die Funktion übergeben und dann in dieser Funktion geändert wird, ändert sich der in der ursprünglichen Variablen gespeicherte Wert nicht. Wenn Sie jedoch den durch die Variable dargestellten Objektwert an die Funktion übergeben und ändern, ändert sich auch, was in dieser Variablen gespeichert ist.
Betrachten Sie das folgende Beispiel:
function primitiveMutator(val) { val = val + 1; } let x = 1; primitiveMutator(x); console.log(x);
Primitive Werte (mit Ausnahme des mysteriösen
NaN
, das nicht gleich sich selbst ist) erweisen sich immer als gleichwertig mit anderen primitiven Werten, die genauso aussehen wie sie selbst. Zum Beispiel:
const first = "abc" + "def"; const second = "ab" + "cd" + "ef"; console.log(first === second);
Die Konstruktion von Objektwerten, die äußerlich gleich aussehen, führt jedoch nicht dazu, dass Entitäten erhalten werden, wenn sie verglichen werden, wird ihre Gleichheit untereinander offenbart. Sie können dies überprüfen, indem Sie:
const obj1 = { name: "Intrinsic" }; const obj2 = { name: "Intrinsic" }; console.log(obj1 === obj2);
Objekte spielen in JavaScript eine grundlegende Rolle. Sie werden buchstäblich überall verwendet. Beispielsweise werden sie häufig in Form von Schlüssel- / Wertsammlungen verwendet. Vor dem Aufkommen des
Symbol
Datentyps konnten jedoch nur Zeichenfolgen als Objektschlüssel verwendet werden. Dies war eine ernsthafte Einschränkung der Verwendung von Objekten in Form von Sammlungen. Beim Versuch, einen Nicht-Zeichenfolgenwert als Objektschlüssel zuzuweisen, wurde dieser Wert in eine Zeichenfolge umgewandelt. Sie können dies überprüfen, indem Sie:
const obj = {}; obj.foo = 'foo'; obj['bar'] = 'bar'; obj[2] = 2; obj[{}] = 'someobj'; console.log(obj);
Übrigens, obwohl dies uns ein wenig vom Thema Zeichen entfernt, möchte ich darauf hinweisen, dass die
Map
Datenstruktur erstellt wurde, um die Verwendung von Schlüssel- / Wertdatenspeichern in Situationen zu ermöglichen, in denen der Schlüssel keine Zeichenfolge ist.
Was ist ein Symbol?
Nachdem wir die Funktionen primitiver Werte in JavaScript herausgefunden haben, können wir endlich über Zeichen sprechen. Ein Symbol ist eine einzigartige primitive Bedeutung. Wenn Sie sich den Symbolen von dieser Position aus nähern, werden Sie feststellen, dass die Symbole in dieser Hinsicht Objekten ähnlich sind, da die Erstellung mehrerer Instanzen der Symbole zur Erstellung unterschiedlicher Werte führt. Aber Symbole sind darüber hinaus unveränderliche primitive Werte. Hier ist ein Beispiel für die Arbeit mit Zeichen:
const s1 = Symbol(); const s2 = Symbol(); console.log(s1 === s2);
Beim Erstellen einer Instanz eines Zeichens können Sie das optionale Argument der ersten Zeichenfolge verwenden. Dieses Argument beschreibt das Symbol, das beim Debuggen verwendet werden soll. Dieser Wert wirkt sich nicht auf das Symbol selbst aus.
const s1 = Symbol('debug'); const str = 'debug'; const s2 = Symbol('xxyy'); console.log(s1 === str);
Symbole als Schlüssel zum Eigentum von Objekten
Symbole können als Eigenschaftsschlüssel für Objekte verwendet werden. Es ist sehr wichtig. Hier ist ein Beispiel für die Verwendung als solche:
const obj = {}; const sym = Symbol(); obj[sym] = 'foo'; obj.bar = 'bar'; console.log(obj);
Bitte beachten Sie, dass durch Zeichen angegebene Schlüssel beim
Object.keys()
der
Object.keys()
-Methode nicht zurückgegeben werden. Code, der vor dem Erscheinen von Zeichen in JS geschrieben wurde, weiß nichts über sie. Daher sollten Informationen zu den Schlüsseln von Objekten, die durch Zeichen dargestellt werden, nicht von der alten
Object.keys()
-Methode zurückgegeben werden.
Auf den ersten Blick scheint es so, als könnten Sie mit den oben genannten Funktionen von Zeichen private Eigenschaften von JS-Objekten erstellen. In vielen anderen Programmiersprachen können Sie mithilfe von Klassen Eigenschaften für versteckte Objekte erstellen. Das Fehlen dieser Funktion wurde lange Zeit als einer der Mängel von JavaScript angesehen.
Leider kann der Code, der mit Objekten arbeitet, frei auf ihre Zeichenfolgenschlüssel zugreifen. Der Code kann darüber hinaus auch auf durch Zeichen angegebene Schlüssel zugreifen, selbst wenn der Code, von dem aus sie mit dem Objekt arbeiten, keinen Zugriff auf das entsprechende Zeichen hat. Mit der
Reflect.ownKeys()
-Methode können Sie beispielsweise eine Liste aller Schlüssel eines Objekts
Reflect.ownKeys()
, sowohl der Zeichenfolgen als auch der Zeichen:
function tryToAddPrivate(o) { o[Symbol('Pseudo Private')] = 42; } const obj = { prop: 'hello' }; tryToAddPrivate(obj); console.log(Reflect.ownKeys(obj));
Beachten Sie, dass derzeit daran gearbeitet wird, Klassen mit der Möglichkeit auszustatten, private Eigenschaften zu verwenden. Diese Funktion wird als
private Felder bezeichnet . Es ist wahr, dass nicht alle Objekte betroffen sind, sondern nur diejenigen, die auf der Grundlage zuvor vorbereiteter Klassen erstellt wurden. Unterstützung für private Felder ist bereits in der Chrome-Browserversion 72 und älter verfügbar.
Verhindern Sie Kollisionen von Objekteigenschaftsnamen
Symbole bieten JavaScript natürlich nicht die Möglichkeit, private Eigenschaften von Objekten zu erstellen, aber sie sind aus anderen Gründen eine wertvolle Neuerung in der Sprache. Sie sind nämlich in Situationen nützlich, in denen bestimmte Bibliotheken Eigenschaften zu Objekten hinzufügen müssen, die außerhalb von ihnen beschrieben wurden, und gleichzeitig keine Angst vor einer Kollision der Namen von Eigenschaften von Objekten haben müssen.
Stellen Sie sich ein Beispiel vor, in dem zwei verschiedene Bibliotheken einem Objekt Metadaten hinzufügen möchten. Möglicherweise müssen beide Bibliotheken das Objekt mit einigen Bezeichnern ausstatten. Wenn Sie für den Namen einer solchen Eigenschaft einfach eine
id
Zeichenfolge aus zwei Buchstaben verwenden, kann es vorkommen, dass eine Bibliothek die von der anderen angegebene Eigenschaft überschreibt.
function lib1tag(obj) { obj.id = 42; } function lib2tag(obj) { obj.id = 369; }
Wenn wir die Symbole in unserem Beispiel verwenden, kann jede Bibliothek bei der Initialisierung die benötigten Symbole generieren. Diese Symbole können dann verwendet werden, um Objekten Eigenschaften zuzuweisen und auf diese Eigenschaften zuzugreifen.
const library1property = Symbol('lib1'); function lib1tag(obj) { obj[library1property] = 42; } const library2property = Symbol('lib2'); function lib2tag(obj) { obj[library2property] = 369; }
Wenn Sie sich ein solches Szenario ansehen, können Sie vom Auftreten von Zeichen in JavaScript profitieren.
Es kann jedoch eine Frage bezüglich der Verwendung von Bibliotheken für die Namen von Eigenschaften von Objekten, zufälligen Zeichenfolgen oder Zeichenfolgen mit einer komplexen Struktur geben, einschließlich beispielsweise des Namens der Bibliothek. Ähnliche Zeichenfolgen können so etwas wie Namespaces für von Bibliotheken verwendete Bezeichner bilden. Zum Beispiel könnte es so aussehen:
const library1property = uuid();
Im Allgemeinen können Sie dies tun. Ähnliche Ansätze sind in der Tat sehr ähnlich zu dem, was bei der Verwendung von Symbolen passiert. Und wenn einige Bibliotheken unter Verwendung von zufälligen Bezeichnern oder Namespaces nicht zufällig dieselben Eigenschaftsnamen generieren, gibt es keine Probleme mit den Namen.
Ein kluger Leser würde jetzt sagen, dass die beiden Ansätze zur Benennung von Objekteigenschaften nicht vollständig gleichwertig sind. Eigenschaftsnamen, die zufällig generiert werden oder Namespaces verwenden, haben einen Nachteil: Die entsprechenden Schlüssel sind sehr leicht zu finden, insbesondere wenn der Code die Schlüssel von Objekten durchsucht oder sie serialisiert. Betrachten Sie das folgende Beispiel:
const library2property = 'LIB2-NAMESPACE-id';
Wenn in dieser Situation ein Symbol für den Schlüsselnamen verwendet würde, würde die JSON-Darstellung des Objekts den Symbolwert nicht enthalten. Warum ist das so? Tatsache ist, dass die Tatsache, dass ein neuer Datentyp in JavaScript angezeigt wurde, nicht bedeutet, dass Änderungen an der JSON-Spezifikation vorgenommen wurden. JSON unterstützt als Eigenschaftsschlüssel nur Zeichenfolgen. Bei der Serialisierung eines Objekts wird nicht versucht, die Zeichen auf besondere Weise darzustellen.
Das betrachtete Problem beim
Object.defineProperty()
von Eigenschaftsnamen in der JSON-Darstellung von Objekten kann mithilfe von
Object.defineProperty()
gelöst werden:
const library2property = uuid();
Zeichenfolgenschlüssel, die „versteckt“ sind, indem ihr
enumerable
auf
false
verhalten sich ähnlich wie Schlüssel, die durch Zeichen dargestellt werden. Beide werden beim
Object.keys()
nicht angezeigt, und beide können mit
Reflect.ownKeys()
erkannt werden. So sieht es aus:
const obj = {}; obj[Symbol()] = 1; Object.defineProperty(obj, 'foo', { enumberable: false, value: 2 }); console.log(Object.keys(obj));
Hier muss ich sagen, dass wir die Möglichkeiten von Symbolen mit anderen Mitteln von JS fast neu erschaffen haben. Insbesondere fallen sowohl durch Symbole dargestellte Schlüssel als auch private Schlüssel nicht in die JSON-Darstellung eines Objekts. Beide können anhand der
Reflect.ownKeys()
-Methode erkannt werden. Infolgedessen können beide nicht als wirklich privat bezeichnet werden. Wenn wir davon ausgehen, dass einige zufällige Werte oder Bibliotheksnamespaces zum Generieren von Schlüsselnamen verwendet werden, bedeutet dies, dass wir das Risiko von Namenskollisionen beseitigt haben.
Es gibt jedoch einen kleinen Unterschied zwischen der Verwendung von Symbolnamen und Namen, die mit anderen Mechanismen erstellt wurden. Da Zeichenfolgen unveränderlich sind und Zeichen garantiert eindeutig sind, besteht immer die Möglichkeit, dass jemand nach Durchlaufen aller möglichen Zeichenkombinationen in einer Zeichenfolge eine Namenskollision verursacht. Aus mathematischer Sicht bedeutet dies, dass Zeichen uns wirklich eine wertvolle Gelegenheit bieten, die Strings nicht haben.
Wenn in Node.js beim Untersuchen von Objekten (z. B. mit
console.log()
) eine Objektmethode namens
console.log()
erkannt wird, wird diese Methode verwendet, um eine Zeichenfolgendarstellung des Objekts
console.log()
und diese dann auf dem Bildschirm anzuzeigen. Es ist leicht zu verstehen, dass dies absolut nicht jeder berücksichtigen kann. Daher kann ein solches Verhalten des Systems zu einem Aufruf der
inspect
Object-Methode führen, mit der Probleme gelöst werden sollen, die nicht mit der Bildung der Zeichenfolgendarstellung des Objekts zusammenhängen. Diese Funktion ist in Node.js 10 veraltet. In Version 11 werden Methoden mit einem ähnlichen Namen einfach ignoriert.
require('util').inspect.custom
diese Funktion zu implementieren, wird nun das
require('util').inspect.custom
. Dies bedeutet, dass niemand jemals in der Lage sein wird, das System versehentlich zu stören, indem er eine Objektmethode namens
inspect
.
Nachahmung von Privateigentum
Hier ist ein interessanter Ansatz, mit dem Sie die privaten Eigenschaften von Objekten simulieren können. Dieser Ansatz beinhaltet die Verwendung einer weiteren modernen JavaScript-Funktion - Proxy-Objekte. Solche Objekte dienen als Wrapper für andere Objekte, mit denen der Programmierer in die mit diesen Objekten ausgeführten Aktionen eingreifen kann.
Proxy-Objekte bieten viele Möglichkeiten, die an Objekten ausgeführten Aktionen abzufangen. Wir sind an der Möglichkeit interessiert, den Vorgang des Lesens von Schlüsseln eines Objekts zu steuern. Wir werden hier nicht auf Details zu Proxy-Objekten eingehen. Wenn Sie interessiert sind, schauen Sie sich
diese Publikation an.
Wir können Proxys verwenden, um zu steuern, welche Eigenschaften des Objekts von außen sichtbar sind. In diesem Fall möchten wir einen Proxy erstellen, der zwei uns bekannte Eigenschaften verbirgt. Einer hat den String-Namen
_favColor
und der zweite wird durch ein Zeichen dargestellt, das in die Variable
favBook
wird:
let proxy; { const favBook = Symbol('fav book'); const obj = { name: 'Thomas Hunter II', age: 32, _favColor: 'blue', [favBook]: 'Metro 2033', [Symbol('visible')]: 'foo' }; const handler = { ownKeys: (target) => { const reportedKeys = []; const actualKeys = Reflect.ownKeys(target); for (const key of actualKeys) { if (key === favBook || key === '_favColor') { continue; } reportedKeys.push(key); } return reportedKeys; } }; proxy = new Proxy(obj, handler); } console.log(Object.keys(proxy));
Der Umgang mit einer Eigenschaft, deren Name durch die Zeichenfolge
_favColor
ist nicht schwierig: Lesen Sie einfach den Quellcode. Dynamische Tasten (wie die oben gezeigten UUID-Tasten) können mit Brute Force abgeglichen werden. Ohne Bezugnahme auf das Symbol können Sie jedoch nicht über das
proxy
Objekt auf den Wert von
Metro 2033
zugreifen.
Es ist zu beachten, dass es in Node.js eine Funktion gibt, die die Privatsphäre von Proxy-Objekten verletzt. Diese Funktion ist in der Sprache selbst nicht vorhanden und daher für andere JS-Laufzeiten, z. B. einen Browser, nicht relevant. Tatsache ist, dass Sie mit dieser Funktion auf das hinter dem Proxy-Objekt versteckte Objekt zugreifen können, wenn Sie Zugriff auf das Proxy-Objekt haben. Hier ist ein Beispiel, das die Fähigkeit demonstriert, die im vorherigen Codefragment gezeigten Mechanismen zu umgehen:
const [originalObject] = process .binding('util') .getProxyDetails(proxy); const allKeys = Reflect.ownKeys(originalObject); console.log(allKeys[3]);
Um die Verwendung dieser Funktion in einer bestimmten Instanz von Node.js zu verhindern, müssen Sie entweder das globale
Reflect
Objekt oder die Bindung des
util
Prozesses
util
. Dies ist jedoch eine andere Aufgabe. Wenn Sie interessiert sind, lesen Sie
diesen Beitrag zum Schutz von JavaScript-basierten APIs.
Zusammenfassung
In diesem Artikel haben wir über den Datentyp
Symbol
gesprochen, darüber, welche Funktionen JavaScript-Entwicklern zur Verfügung gestellt werden und welche vorhandenen Sprachmechanismen zur Simulation dieser Funktionen verwendet werden können.
Liebe Leser! Verwenden Sie Symbole in Ihren JavaScript-Projekten?
