Eine detaillierte Übersicht bekannter Symbole





Guten Tag, Freunde!



Symbol ist ein primitiver Datentyp, der in ECMAScript2015 (ES6) eingeführt wurde und mit dem Sie eindeutige Bezeichner erstellen können: const uniqueKey = Symbol ('SymbolName').



Sie können Symbole als Schlüssel für Objekteigenschaften verwenden. Symbole, die JavaScript auf besondere Weise behandelt, werden als bekannte Symbole bezeichnet . Diese Zeichen werden von integrierten JavaScript-Algorithmen verwendet. Beispielsweise wird Symbol.iterator verwendet, um die Elemente von Arrays und Zeichenfolgen zu durchlaufen. Es kann auch verwendet werden, um Ihre eigenen Iteratorfunktionen zu definieren.



Diese Symbole spielen eine wichtige Rolle, da Sie damit das Verhalten von Objekten optimieren können.



Die Verwendung von Symbolen als Objektschlüssel (anstelle von Zeichenfolgen) erleichtert das Hinzufügen neuer Funktionen zu Objekten. Gleichzeitig müssen Sie sich keine Gedanken über Kollisionen zwischen Schlüsseln machen (da jedes Zeichen einzigartig ist), was bei der Verwendung von Zeichenfolgen zu einem Problem werden kann.



Dieser Artikel konzentriert sich auf bekannte Symbole mit Beispielen für deren Verwendung.



Der Einfachheit halber hat die Syntax des bekannten Symbols. <Name> -Symbole das Format @@ <Name>. Beispielsweise wird Symbol.iterator als @@ Iterator, Symbol.toPrimitive als @@ toPrimitive usw. dargestellt.



Wenn wir sagen, dass ein Objekt eine @@ Iterator-Methode hat, enthält das Objekt eine Eigenschaft namens Symbol.iterator, die durch eine Funktion dargestellt wird: {[Symbol.iterator]: function () {}}.



1. Kurze Einführung in Symbole



Ein Zeichen ist ein primitiver Typ (z. B. eine Zahl, eine Zeichenfolge oder ein Boolescher Wert), der eindeutig und unveränderlich (unveränderlich) ist.



Um ein Symbol zu erstellen, rufen Sie die Funktion Symbol () mit einem optionalen Argument auf - dem Namen oder genauer der Beschreibung des Symbols:



const mySymbol = Symbol()
const namedSymbol = Symbol('myName')
typeof mySymbol // symbol
typeof namedSymbol // symbol


mySymbol und namedSymbol sind primitive Symbole. namedSymbol heißt 'myName' und wird normalerweise zum Debuggen von Code verwendet.



Jeder Aufruf von Symbol () erstellt ein neues eindeutiges Symbol. Zwei Zeichen sind eindeutig (oder speziell), auch wenn sie denselben Namen haben:



const first = Symbol()
const second = Symbol()
first === second // false

const firstNamed = Symbol('Lorem')
const secondNamed = Symbol('Lorem')
firstNamed === secondNamed // false


Symbole können Schlüssel von Objekten sein. Verwenden Sie dazu die berechnete Eigenschaftssyntax ([Symbol]) in einem Objektliteral oder einer Klassendefinition:



const strSymbol = Symbol('String')

const myObj = {
  num: 1,
  [strSymbol]: 'Hello World'
}

myObj[strSymbol] // Hello World
Object.getOwnPropertyNames(myObj) // ['num']
Object.getOwnPropertySymbols(myObj) // [Symbol(String)]


Symboleigenschaften können nicht mit Object.keys () oder Object.getOwnPropertyNames () abgerufen werden. Um darauf zugreifen zu können, müssen Sie die Sonderfunktion Object.getOwnPropertySymbols () verwenden.



Durch die Verwendung bekannter Symbole als Schlüssel können Sie das Verhalten von Objekten ändern.



Bekannte Symbole sind als nicht aufzählbare, unveränderliche und nicht konfigurierbare Eigenschaften des Symbolobjekts verfügbar. Verwenden Sie zum Abrufen die Punktnotation: Symbol.iterator, Symbol.hasInstance usw.



So erhalten Sie eine Liste bekannter Symbole:



Object.getOwnPropertyNames(Symbol)
// ["hasInstance", "isConcatSpreadable", "iterator", "toPrimitive",
//  "toStringTag", "unscopables", "match", "replace", "search",
//  "split", "species", ...]

typeof Symbol.iterator // symbol


Object.getOwnPropertyNames (Symbol) gibt eine Liste der nativen Eigenschaften des Symbol-Objekts zurück, einschließlich bekannter Symbole. Symbol.iterator ist natürlich vom Typ Symbol.



2. @@ Iterator, mit dem Sie Objekte iterierbar machen können (iterierbar)



Symbol.iterator ist vielleicht das bekannteste Symbol. Hier können Sie definieren, wie ein Objekt mithilfe einer for-of-Anweisung oder eines Spread-Operators wiederholt werden soll (und ob es überhaupt wiederholt werden soll).



Viele integrierte Typen wie Zeichenfolgen, Arrays, Maps, Sets oder Sets sind standardmäßig iterierbar, da sie über eine @@ Iterator-Methode verfügen:



const myStr = 'Hi'
typeof myStr[Symbol.iterator] // function
for (const char of myStr) {
  console.log(char) //     :  'H',  'i'
}
[...myStr] // ['H', 'i']


Die Variable myStr enthält eine primitive Zeichenfolge mit der Eigenschaft Symbol.iterator. Diese Eigenschaft enthält eine Funktion, mit der Zeichen in einer Zeichenfolge durchlaufen werden.



Das Objekt, in dem die Symbol.iterator-Methode definiert ist, muss dem Iterationsprotokoll (Iterator) entsprechen . Genauer gesagt muss diese Methode ein Objekt zurückgeben, das dem angegebenen Protokoll entspricht. Ein solches Objekt muss über eine next () -Methode verfügen, die {value: <iterator_value>, done: <boolean_finished_iterator>} zurückgibt.



Im folgenden Beispiel erstellen wir ein iterierbares myMethods-Objekt, um seine Methoden zu durchlaufen:



function methodsIterator() {
  let index = 0
  const methods = Object.keys(this)
    .filter(key => typeof this[key] === 'function')

    return {
      next: () => ({
        done: index === methods.length,
        value: methods[index++]
      })
    }
}

const myMethods = {
  toString: () => '[object myMethods]',
  sum: (a, b) => a + b,
  numbers: [1, 3, 5],
  [Symbol.iterator]: methodsIterator
}

for (const method of myMethods) {
  console.log(method) // toString, sum
}


MethodsIterator () ist eine Funktion, die einen Iterator {next: function () {}} zurückgibt. Das myMethods-Objekt definiert eine berechnete Eigenschaft [Symbol.iterator] mit dem Wert MethodsIterator. Dies macht das Objekt mit einer for-of-Schleife iterierbar. Objektmethoden können auch mit [... myMethods] abgerufen werden. Ein solches Objekt kann mit Array.from (myMethods) in ein Array konvertiert werden.



Die Erstellung eines iterierbaren Objekts kann mithilfe einer Generatorfunktion vereinfacht werden . Diese Funktion gibt ein Generator- Objekt zurück , das dem Iterationsprotokoll entspricht.



Erstellen wir eine Fibonacci-Klasse mit einer @@ Iterator-Methode, die eine Folge von Fibonacci-Zahlen generiert:



class Fibonacci {
  constructor(n) {
    this.n = n
  }

  *[Symbol.iterator]() {
    let a = 0, b = 1, index = 0
    while (index < this.n) {
      index++
      let current = a
      a = b
      b = current + a
      yield current
    }
  }
}

const sequence = new Fibonacci(6)
const numbers = [...sequence]
console.log(numbers) // [0, 1, 1, 2, 3, 5]


* [Symbol.iterator] () {} definiert eine Klassenmethode - eine Generatorfunktion. Die Fibonacci-Instanz entspricht dem Brute-Force-Protokoll. Der Spread-Operator ruft die Iterator-Methode @@ auf, um ein Array von Zahlen zu erstellen.



Wenn ein primitiver Typ oder ein primitives Objekt @@ iterator enthält, kann es in den folgenden Szenarien verwendet werden:



  • Durchlaufen von Elementen mit for-of
  • Erstellen eines Arrays von Elementen mit dem Spread-Operator
  • Erstellen eines Arrays mit Array.from (iterableObject)
  • In einem Yield * -Ausdruck an einen anderen Generator übergeben
  • In den Konstruktoren Map (), WeakMap (), Set () und WeakSet ()
  • In statischen Methoden Promise.all (), Promise.race () usw.


Weitere Informationen zum Erstellen eines iterierbaren Objekts finden Sie hier .



3. @@ hasInstance zum Konfigurieren der Instanz von



Standardmäßig prüft die obj-Instanz des Constructor-Operators, ob sich in der obj-Prototypenkette ein Constructor.prototype-Objekt befindet. Betrachten wir ein Beispiel:



function Constructor() {
  // ...
}
const obj = new Constructor()
const objProto = Object.getPrototypeOf(obj)

objProto === Constructor.prototype // true
obj instanceof Constructor // true
obj instanceof Object // true


Die obj-Instanz von Constructor gibt true zurück, da der Prototyp von obj Constructor.prototype ist (als Ergebnis des Aufrufs des Konstruktors). instanceof bezieht sich bei Bedarf auf die Prototypkette, sodass obj instanceof Object auch true zurückgibt.



Manchmal erfordert Ihre Anwendung eine strengere Instanzprüfung.



Glücklicherweise haben wir die Möglichkeit, eine @@ hasInstance-Methode zu definieren, um das Verhalten von instanceof zu ändern. obj instanceof Type entspricht Type [Symbol.hasInstance] (obj).



Lassen Sie uns überprüfen, ob die Variablen iterierbar sind:



class Iterable {
  static [Symbol.hasInstance](obj) {
    return typeof obj[Symbol.iterator] === 'function'
  }
}

const arr = [1, 3, 5]
const str = 'Hi'
const num = 21
arr instanceof Iterable // true
str instanceof Iterable // true
num instanceof Iterable // false


Die Iterable-Klasse enthält eine statische Methode @@ hasInstance. Diese Methode prüft, ob obj iterierbar ist, d.h. ob es eine Symbol.iterator-Eigenschaft enthält. arr und str sind iterierbar, num jedoch nicht.



4. @@ toPrimitive, um ein Objekt in ein Grundelement umzuwandeln



Verwenden Sie Symbol.toPrimitive, um eine Eigenschaft zu definieren, deren Wert ein Objekt zur primitiven Konvertierungsfunktion ist. @@ toPrimitive verwendet einen Parameter, einen Hinweis, der Zahl, Zeichenfolge oder Standard sein kann. Hinweis gibt den Typ des Rückgabewerts an.



Lassen Sie uns die Transformation des Arrays verbessern:



function arrayToPrimitive(hint) {
  if (hint === 'number') {
    return this.reduce((x, y) => x + y)
  } else if (hint === 'string') {
    return `[${this.join(', ')}]`
  } else {
    // hint    
    return this.toString()
  }
}

const array = [1, 3, 5]
array[Symbol.toPrimitive] = arrayToPrimitive

//    . hint  
+ array // 9
//    . hint  
`array is ${array}` // array is [1, 3, 5]
//   . hint   default
'array elements: ' + array // array elements: 1,3,5


arrayToPrimitive (Hinweis) ist eine Funktion, die ein Array basierend auf dem Hinweiswert in ein Grundelement konvertiert. Wenn Sie das Array [Symbol.toPrimitive] auf arrayToPrimitive setzen, wird das Array gezwungen, die neue Transformationsmethode zu verwenden. Wenn Sie + Array ausführen, wird @@ toPrimitive mit dem Hinweiswert number aufgerufen. Die Summe der Array-Elemente wird zurückgegeben. Array ist $ {Array} ruft @@ toPrimitive mit Hinweis = Zeichenfolge auf. Das Array wird in die Zeichenfolge '[1, 3, 5]' konvertiert. Schließlich 'Array-Elemente:' + Array verwendet Hinweis = Standard zum Transformieren. Das Array wird in '1,3,5' konvertiert.



Die Methode @@ toPrimitive wird verwendet, um ein Objekt als primitiven Typ darzustellen:



  • Bei Verwendung des losen (abstrakten) Gleichheitsoperators: object == primitive
  • Bei Verwendung des Additions- / Verkettungsoperators: Objekt + Grundelement
  • Bei Verwendung des Subtraktionsoperators: Objekt - Grundelement
  • In verschiedenen Situationen wird ein Objekt in ein Grundelement konvertiert: String (Objekt), Number (Objekt) usw.


5. @@ toStringTag, um eine Standardobjektbeschreibung zu erstellen



Verwenden Sie Symbol.toStringTag, um eine Eigenschaft zu definieren, deren Wert eine Zeichenfolge ist, die den Typ des Objekts beschreibt. Die Methode @@ toStringTag wird von Object.prototype.toString () verwendet.



Die Spezifikation definiert die von Object.prototype.toString () zurückgegebenen Standardwerte für viele Typen:



const toString = Object.prototype.toString
toString.call(undefined) // [object Undefined]
toString.call(null)      // [object Null]
toString.call([1, 4])    // [object Array]
toString.call('Hello')   // [object String]
toString.call(15)        // [object Number]
toString.call(true)      // [object Boolean]
// Function, Arguments, Error, Date, RegExp  ..
toString.call({})        // [object Object]


Diese Typen haben keine Symbol.toStringTag-Eigenschaft, da der Object.prototype.toString () -Algorithmus sie auf besondere Weise auswertet.



Die betreffende Eigenschaft wird in Typen wie Symbolen, Generatorfunktionen, Karten, Versprechungen usw. definiert. Betrachten Sie ein Beispiel:



const toString = Object.prototype.toString
const noop = function() { }

Symbol.iterator[Symbol.toStringTag]   // Symbol
(function* () {})[Symbol.toStringTag] // GeneratorFunction
new Map()[Symbol.toStringTag]         // Map
new Promise(noop)[Symbol.toStringTag] // Promise

toString.call(Symbol.iterator)   // [object Symbol]
toString.call(function* () {})   // [object GeneratorFunction]
toString.call(new Map())         // [object Map]
toString.call(new Promise(noop)) // [object Promise]


Wenn das Objekt keiner Standardtypgruppe angehört und die Eigenschaft @@ toStringTag nicht enthält, wird Object zurückgegeben. Natürlich können wir dies ändern:



const toString = Object.prototype.toString

class SimpleClass { }
toString.call(new SimpleClass) // [object Object]

class MyTypeClass {
  constructor() {
    this[Symbol.toStringTag] = 'MyType'
  }
}

toString.call(new MyTypeClass) // [object MyType]


Eine Instanz der SimpleClass-Klasse hat keine @@ toStringTag-Eigenschaft, daher gibt Object.prototype.toString () [Objektobjekt] zurück. Der Konstruktor der MyTypeClass-Klasse weist der Instanz die Eigenschaft @@ toStringTag mit dem Wert MyType zu, sodass Object.prototype.toString () [Objekt MyType] zurückgibt.



Beachten Sie, dass @@ toStringTag aus Gründen der Abwärtskompatibilität eingeführt wurde. Seine Verwendung ist unerwünscht. Es ist besser, instanceof (zusammen mit @@ hasInstance) oder typeof zu verwenden, um den Typ eines Objekts zu bestimmen.



6. @@ Spezies, um ein abgeleitetes Objekt zu erstellen



Verwenden Sie Symbol.species, um eine Eigenschaft zu definieren, deren Wert eine Konstruktorfunktion ist, mit der abgeleitete Objekte erstellt werden.



Der @@ Spezieswert vieler Konstruktoren ist der Konstruktor selbst:



Array[Symbol.species] === Array // true
Map[Symbol.species] === Map // true
RegExp[Symbol.species] === RegExp // true


Beachten Sie zunächst, dass ein abgeleitetes Objekt ein Objekt ist, das zurückgegeben wird, nachdem eine bestimmte Operation für das ursprüngliche Objekt ausgeführt wurde. Wenn Sie beispielsweise map () aufrufen, wird ein abgeleitetes Objekt zurückgegeben - das Ergebnis der Transformation der Elemente des Arrays.



Abgeleitete Objekte beziehen sich normalerweise auf denselben Konstruktor wie die ursprünglichen Objekte. Manchmal ist es jedoch erforderlich, einen anderen Konstruktor (möglicherweise eine der Standardklassen) zu definieren: Hier kann @@ species helfen.



Angenommen, wir erweitern den Array-Konstruktor um die untergeordnete MyArray-Klasse, um einige nützliche Methoden hinzuzufügen. Dabei möchten wir, dass der Konstruktor der abgeleiteten Objekte der MyArray-Instanz Array ist. Dazu müssen Sie eine berechnete Eigenschaft @@ species mit einem Array-Wert definieren:



class MyArray extends Array {
  isEmpty() {
    return this.length === 0
  }
  static get [Symbol.species]() {
    return Array
  }
}
const array = new MyArray(2, 3, 5)
array.isEmpty() // false
const odds = array.filter(item => item % 2 === 1)
odds instanceof Array // true
odds instanceof MyArray // false


MyArray definiert die statisch berechnete Eigenschaft Symbol.species. Es gibt an, dass der Konstruktor für abgeleitete Objekte der Array-Konstruktor sein soll. Später, wenn die Elemente eines Arrays gefiltert werden, gibt array.filter () ein Array zurück.



Die berechnete Eigenschaft @@ species wird von Array- und typisierten Array-Methoden wie map (), concat (), Slice (), splice () verwendet, die abgeleitete Objekte zurückgeben. Die Verwendung dieser Eigenschaft kann nützlich sein, um Karten, reguläre Ausdrücke oder Versprechen zu erweitern und gleichzeitig den ursprünglichen Konstruktor beizubehalten.



7. Erstellen Sie einen regulären Ausdruck in Form eines Objekts: @@ match, @@ replace, @@ search und @@ split



Der String-Prototyp enthält 4 Methoden, die reguläre Ausdrücke als Argument verwenden:



  • String.prototype.match (regExp)
  • String.prototype.replace (regExp, newSubstr)
  • String.prototype.search (regExp)
  • String.prototype.split (regExp, limit)


Mit ES6 können diese Methoden andere Typen akzeptieren, indem die entsprechenden berechneten Eigenschaften definiert werden: @@ match, @@ replace, @@ search und @@ split.



Seltsamerweise enthält der RegExp-Prototyp die angegebenen Methoden, die auch mithilfe von Symbolen definiert werden:



typeof RegExp.prototype[Symbol.match]   // function
typeof RegExp.prototype[Symbol.replace] // function
typeof RegExp.prototype[Symbol.search]  // function
typeof RegExp.prototype[Symbol.split]   // function


Im folgenden Beispiel definieren wir eine Klasse, die anstelle eines regulären Ausdrucks verwendet werden kann:



class Expression {
  constructor(pattern) {
    this.pattern = pattern
  }
  [Symbol.match](str) {
    return str.includes(this.pattern)
  }
  [Symbol.replace](str, replace) {
    return str.split(this.pattern).join(replace)
  }
  [Symbol.search](str) {
    return str.indexOf(this.pattern)
  }
  [Symbol.split](str) {
    return str.split(this.pattern)
  }
}

const sunExp = new Expression('')

' '.match(sunExp) // true
' '.match(sunExp) // false
' day'.replace(sunExp, '') // ' '
'  '.search(sunExp) // 8
''.split(sunExp) // ['', '']


Die Expression-Klasse definiert die Methoden @@ match, @@ replace, @@ search und @@ split. Dann wird eine Instanz dieser Klasse - sunExp - in den entsprechenden Methoden anstelle eines regulären Ausdrucks verwendet.



8. @@ isConcatSpreadable zum Konvertieren eines Objekts in ein Array



Symbol.isConcatSpreadable ist ein boolescher Wert, der angibt, dass das Objekt mit der Methode Array.prototype.concat () in ein Array konvertiert werden kann.



Standardmäßig ruft die concat () -Methode die Elemente des Arrays ab (zerlegt das Array in die Elemente, aus denen es besteht), wenn die Arrays verkettet werden:



const letters = ['a', 'b']
const otherLetters = ['c', 'd']
otherLetters.concat('e', letters) // ['c', 'd', 'e', 'a', 'b']


Übergeben Sie zum Verketten der beiden Arrays Buchstaben als Argument an die Methode concat (). Elemente des Buchstabenarrays werden Teil des Ergebnisses der Verkettung: ['c', 'd', 'e', ​​'a', 'b'].



Um zu verhindern, dass das Array in Elemente zerlegt wird, und um das Array unverändert zum Teil des Union-Ergebnisses zu machen, sollte die Eigenschaft @@ isConcatSpreadable auf false gesetzt werden:



const letters = ['a', 'b']
letters[Symbol.isConcatSpreadable] = false
const otherLetters = ['c', 'd']
otherLetters.concat('e', letters) // ['c', 'd', 'e', ['a', 'b']]


Im Gegensatz zu einem Array zerlegt die concat () -Methode keine arrayartigen Objekte in Elemente. Dieses Verhalten kann auch mit @@ isConcatSpreadable geändert werden:



const letters = { 0: 'a', 1: 'b', length: 2 }
const otherLetters = ['c', 'd']
otherLetters.concat('e', letters)
// ['c', 'd', 'e', {0: 'a', 1: 'b', length: 2}]
letters[Symbol.isConcatSpreadable] = true
otherLetters.concat('e', letters) // ['c', 'd', 'e', 'a', 'b']


9. @@ unscopables für den Zugriff auf Eigenschaften mit



Symbol.unscopables ist eine berechnete Eigenschaft, deren Eigennamen von dem Objekt ausgeschlossen werden, das am Anfang der Bereichskette mithilfe der with-Anweisung hinzugefügt wurde. Die Eigenschaft @@ unscopables hat das folgende Format: {propertyName: <boolean_exclude_binding>}.



ES6 definiert @@ unscopables nur für Arrays. Dies geschieht, um neue Methoden auszublenden, die gleichnamige Variablen im alten Code überschreiben können:



Array.prototype[Symbol.unscopables]
// { copyWithin: true, entries: true, fill: true,
//   find: true, findIndex: true, keys: true }
let numbers = [1, 3, 5]
with (numbers) {
  concat(7) // [1, 3, 5, 7]
  entries // ReferenceError: entries is not defined
}


Wir können auf die concat () -Methode im with-Body zugreifen, da diese Methode nicht in der Eigenschaft @@ unscopables enthalten ist. Die Methode settings () wird in dieser Eigenschaft angegeben und auf true gesetzt, sodass sie innerhalb von nicht verfügbar ist.



@@ unscopables wurde ausschließlich aus Gründen der Abwärtskompatibilität mit Legacy-Code mithilfe der with-Anweisung eingeführt (im strengen Modus veraltet und nicht zulässig).



Ich hoffe, Sie haben etwas Interessantes für sich gefunden. Vielen Dank für Ihre Aufmerksamkeit.



All Articles