Movatterモバイル変換


[0]ホーム

URL:


7. Symbols
Table of contents
Please support this book:buy it (PDF, EPUB, MOBI) ordonate
(Ad, please don’t block.)

7.Symbols



7.1Overview

Symbols are a new primitive type in ECMAScript 6. They are created via a factory function:

constmySymbol=Symbol('mySymbol');

Every time you call the factory function, a new and unique symbol is created. The optional parameter is a descriptive string that is shown when printing the symbol (it has no other purpose):

> mySymbolSymbol(mySymbol)

7.1.1Use case 1: unique property keys

Symbols are mainly used as unique property keys – a symbol never clashes with any other property key (symbol or string). For example, you can make an objectiterable (usable via thefor-of loop and other language mechanisms), by using the symbol stored inSymbol.iterator as the key of a method (more information on iterables is given inthe chapter on iteration):

constiterableObject={[Symbol.iterator](){// (A)···}}for(constxofiterableObject){console.log(x);}// Output:// hello// world

In line A, a symbol is used as the key of the method. This unique marker makes the object iterable and enables us to use thefor-of loop.

7.1.2Use case 2: constants representing concepts

In ECMAScript 5, you may have used strings to represent concepts such as colors. In ES6, you can use symbols and be sure that they are always unique:

constCOLOR_RED=Symbol('Red');constCOLOR_ORANGE=Symbol('Orange');constCOLOR_YELLOW=Symbol('Yellow');constCOLOR_GREEN=Symbol('Green');constCOLOR_BLUE=Symbol('Blue');constCOLOR_VIOLET=Symbol('Violet');functiongetComplement(color){switch(color){caseCOLOR_RED:returnCOLOR_GREEN;caseCOLOR_ORANGE:returnCOLOR_BLUE;caseCOLOR_YELLOW:returnCOLOR_VIOLET;caseCOLOR_GREEN:returnCOLOR_RED;caseCOLOR_BLUE:returnCOLOR_ORANGE;caseCOLOR_VIOLET:returnCOLOR_YELLOW;default:thrownewException('Unknown color: '+color);}}

Every time you callSymbol('Red'), a new symbol is created. Therefore,COLOR_RED can never be mistaken for another value. That would be different if it were the string'Red'.

7.1.3Pitfall: you can’t coerce symbols to strings

Coercing (implicitly converting) symbols to strings throws exceptions:

constsym=Symbol('desc');conststr1=''+sym;// TypeErrorconststr2=`${sym}`;// TypeError

The only solution is to convert explicitly:

conststr2=String(sym);// 'Symbol(desc)'conststr3=sym.toString();// 'Symbol(desc)'

Forbidding coercion prevents some errors, but also makes working with symbols more complicated.

7.1.4Which operations related to property keys are aware of symbols?

The following operations are aware of symbols as property keys:

The following operations ignore symbols as property keys:

7.2A new primitive type

ECMAScript 6 introduces a new primitive type: symbols. They are tokens that serve as unique IDs. You create symbols via the factory functionSymbol() (which is loosely similar toString returning strings if called as a function):

constsymbol1=Symbol();

Symbol() has an optional string-valued parameter that lets you give the newly created Symbol a description. That description is used when the symbol is converted to a string (viatoString() orString()):

> const symbol2 = Symbol('symbol2');> String(symbol2)'Symbol(symbol2)'

Every symbol returned bySymbol() is unique, every symbol has its own identity:

> Symbol() === Symbol()false

You can see that symbols are primitive if you apply thetypeof operator to one of them – it will return a new symbol-specific result:

> typeof Symbol()'symbol'

7.2.1Symbols as property keys

Symbols can be used as property keys:

constMY_KEY=Symbol();constobj={};obj[MY_KEY]=123;console.log(obj[MY_KEY]);// 123

Classes and object literals have a feature calledcomputed property keys: You can specify the key of a property via an expression, by putting it in square brackets. In the following object literal, we use a computed property key to make the value ofMY_KEY the key of a property.

constMY_KEY=Symbol();constobj={[MY_KEY]:123};

A method definition can also have a computed key:

constFOO=Symbol();constobj={[FOO](){return'bar';}};console.log(obj[FOO]());// bar

7.2.2Enumerating own property keys

Given that there is now a new kind of value that can become the key of a property, the following terminology is used for ECMAScript 6:

Let’s examine the APIs for enumerating own property keys by first creating an object.

constobj={[Symbol('my_key')]:1,enum:2,nonEnum:3};Object.defineProperty(obj,'nonEnum',{enumerable:false});

Object.getOwnPropertyNames() ignores symbol-valued property keys:

> Object.getOwnPropertyNames(obj)['enum', 'nonEnum']

Object.getOwnPropertySymbols() ignores string-valued property keys:

> Object.getOwnPropertySymbols(obj)[Symbol(my_key)]

Reflect.ownKeys() considers all kinds of keys:

> Reflect.ownKeys(obj)[Symbol(my_key), 'enum', 'nonEnum']

Object.keys() only considers enumerable property keys that are strings:

> Object.keys(obj)['enum']

The nameObject.keys clashes with the new terminology (only string keys are listed).Object.names orObject.getEnumerableOwnPropertyNames would be a better choice now.

7.3Using symbols to represent concepts

In ECMAScript 5, one often represents concepts (think enum constants) via strings. For example:

varCOLOR_RED='Red';varCOLOR_ORANGE='Orange';varCOLOR_YELLOW='Yellow';varCOLOR_GREEN='Green';varCOLOR_BLUE='Blue';varCOLOR_VIOLET='Violet';

However, strings are not as unique as we’d like them to be. To see why, let’s look at the following function.

functiongetComplement(color){switch(color){caseCOLOR_RED:returnCOLOR_GREEN;caseCOLOR_ORANGE:returnCOLOR_BLUE;caseCOLOR_YELLOW:returnCOLOR_VIOLET;caseCOLOR_GREEN:returnCOLOR_RED;caseCOLOR_BLUE:returnCOLOR_ORANGE;caseCOLOR_VIOLET:returnCOLOR_YELLOW;default:thrownewException('Unknown color: '+color);}}

It is noteworthy that you can use arbitrary expressions asswitch cases, you are not limited in any way. For example:

functionisThree(x){switch(x){case1+1+1:returntrue;default:returnfalse;}}

We use the flexibility thatswitch offers us and refer to the colors via our constants (COLOR_RED etc.) instead of hard-coding them ('Red' etc.).

Interestingly, even though we do so, there can still be mix-ups. For example, someone may define a constant for a mood:

varMOOD_BLUE='Blue';

Now the value ofCOLOR_BLUE is not unique anymore andMOOD_BLUE can be mistaken for it. If you use it as a parameter forgetComplement(), it returns'Orange' where it should throw an exception.

Let’s use symbols to fix this example. Now we can also usethe ES6 featureconst, which lets us declare actual constants (you can’t change what value is bound to a constant, but the value itself may be mutable).

constCOLOR_RED=Symbol('Red');constCOLOR_ORANGE=Symbol('Orange');constCOLOR_YELLOW=Symbol('Yellow');constCOLOR_GREEN=Symbol('Green');constCOLOR_BLUE=Symbol('Blue');constCOLOR_VIOLET=Symbol('Violet');

Each value returned bySymbol is unique, which is why no other value can be mistaken forBLUE now. Intriguingly, the code ofgetComplement() doesn’t change at all if we use symbols instead of strings, which shows how similar they are.

7.4Symbols as keys of properties

Being able to create properties whose keys never clash with other keys is useful in two situations:

7.4.1Symbols as keys of non-public properties

Whenever there are inheritance hierarchies in JavaScript (e.g. created via classes, mixins or a purely prototypal approach), you have two kinds of properties:

For usability’s sake, public properties usually have string keys. But for private properties with string keys, accidental name clashes can become a problem. Therefore, symbols are a good choice. For example, in the following code, symbols are used for the private properties_counter and_action.

const_counter=Symbol('counter');const_action=Symbol('action');classCountdown{constructor(counter,action){this[_counter]=counter;this[_action]=action;}dec(){letcounter=this[_counter];if(counter<1)return;counter--;this[_counter]=counter;if(counter===0){this[_action]();}}}

Note that symbols only protect you from name clashes, not from unauthorized access, because you can find out all own property keys – including symbols – of an object viaReflect.ownKeys(). If you want protection there, as well, you can use one of the approaches listed in Sect. “Private data for classes”.

7.4.2Symbols as keys of meta-level properties

Symbols having unique identities makes them ideal as keys of public properties that exist on a different level than “normal” property keys, because meta-level keys and normal keys must not clash. One example of meta-level properties are methods that objects can implement to customize how they are treated by a library. Using symbol keys protects the library from mistaking normal methods as customization methods.

ES6Iterability is one such customization. An object isiterable if it has a method whose key is the symbol (stored in)Symbol.iterator. In the following code,obj is iterable.

constobj={data:['hello','world'],[Symbol.iterator](){···}};

The iterability ofobj enables you to use thefor-of loop and similar JavaScript features:

for(constxofobj){console.log(x);}// Output:// hello// world

7.4.3Examples of name clashes in JavaScript’s standard library

In case you think that name clashes don’t matter, here are three examples of where name clashes caused problems in the evolution of the JavaScript standard library:

In contrast,adding iterability to an object via the property keySymbol.iterator can’t cause problems, because that key doesn’t clash with anything.

7.5Converting symbols to other primitive types

The following table shows what happens if you explicitly or implicitly convert symbols to other primitive types:

Conversion toExplicit conversionCoercion (implicit conversion)
booleanBoolean(sym) → OK!sym → OK
numberNumber(sym)TypeErrorsym*2TypeError
stringString(sym) → OK''+symTypeError
 sym.toString() → OK`${sym}`TypeError

7.5.1Pitfall: coercion to string

Coercion to string being forbidden can easily trip you up:

constsym=Symbol();console.log('A symbol: '+sym);// TypeErrorconsole.log(`A symbol:${sym}`);// TypeError

To fix these problems, you need an explicit conversion to string:

console.log('A symbol: '+String(sym));// OKconsole.log(`A symbol:${String(sym)}`);// OK

7.5.2Making sense of the coercion rules

Coercion (implicit conversion) is often forbidden for symbols. This section explains why.

7.5.2.1Truthiness checks are allowed

Coercion to boolean is always allowed, mainly to enable truthiness checks inif statements and other locations:

if(value){···}param=param||0;
7.5.2.2Accidentally turning symbols into property keys

Symbols are special property keys, which is why you want to avoid accidentally converting them to strings, which are a different kind of property keys. This could happen if you use the addition operator to compute the name of a property:

myObject['__'+value]

That’s why aTypeError is thrown ifvalue is a symbol.

7.5.2.3Accidentally turning symbols into Array indices

You also don’t want to accidentally turn symbols into Array indices. The following is code where that could happen ifvalue is a symbol:

myArray[1+value]

That’s why the addition operator throws an error in this case.

7.5.3Explicit and implicit conversion in the spec

7.5.3.1Converting to boolean

To explicitly convert a symbol to boolean, you callBoolean(), which returnstrue for symbols:

> const sym = Symbol('hello');> Boolean(sym)true

Boolean() computes its result via the internal operationToBoolean(), which returnstrue for symbols and other truthy values.

Coercion also usesToBoolean():

> !symfalse
7.5.3.2Converting to number

To explicitly convert a symbol to number, you callNumber():

> const sym = Symbol('hello');> Number(sym)TypeError: can't convert symbol to number

Number() computes its result via the internal operationToNumber(), which throws aTypeError for symbols.

Coercion also usesToNumber():

> +symTypeError: can't convert symbol to number
7.5.3.3Converting to string

To explicitly convert a symbol to string, you callString():

> const sym = Symbol('hello');> String(sym)'Symbol(hello)'

If the parameter ofString() is a symbol then it handles the conversion to string itself and returns the stringSymbol() wrapped around the description that was provided when creating the symbol. If no description was given, the empty string is used:

> String(Symbol())'Symbol()'

ThetoString() method returns the same string asString(), but neither of these two operations calls the other one, they both call the same internal operationSymbolDescriptiveString().

> Symbol('hello').toString()'Symbol(hello)'

Coercion is handled via the internal operationToString(), which throws aTypeError for symbols. One method that coerces its parameter to string isNumber.parseInt():

> Number.parseInt(Symbol())TypeError: can't convert symbol to string
7.5.3.4Not allowed: converting via the binary addition operator (+)

Theaddition operator works as follows:

Coercion to either string or number throws an exception, which means that you can’t (directly) use the addition operator for symbols:

> '' + Symbol()TypeError: can't convert symbol to string> 1 + Symbol()TypeError: can't convert symbol to number

7.6Wrapper objects for symbols

While all other primitive values have literals, you need to create symbols by function-callingSymbol. Thus, there is a risk of accidentally invokingSymbol as a constructor. That produces instances ofSymbol, which are not very useful. Therefore, an exception is thrown when you try to do that:

> new Symbol()TypeError: Symbol is not a constructor

There is still a way to create wrapper objects, instances ofSymbol:Object, called as a function, converts all values to objects, including symbols.

> const sym = Symbol();> typeof sym'symbol'> const wrapper = Object(sym);> typeof wrapper'object'> wrapper instanceof Symboltrue

7.6.1Accessing properties via[ ] and wrapped keys

The square bracket operator[ ] normally coerces its operand to string. There are now two exceptions: symbol wrapper objects are unwrapped and symbols are used as they are. Let’s use the following object to examine this phenomenon.

constsym=Symbol('yes');constobj={[sym]:'a',str:'b',};

The square bracket operator unwraps wrapped symbols:

> const wrappedSymbol = Object(sym);> typeof wrappedSymbol'object'> obj[wrappedSymbol]'a'

Like any other value not related to symbols, a wrapped string is converted to a string by the square bracket operator:

> const wrappedString = new String('str');> typeof wrappedString'object'> obj[wrappedString]'b'
7.6.1.1Property access in the spec

The operator for getting and setting properties uses the internal operationToPropertyKey(), which works as follows:

7.7Crossing realms with symbols

This is an advanced topic.

Acode realm (short: realm) is a context in which pieces of code exist. It includes global variables, loaded modules and more. Even though code exists “inside” exactly one realm, it may have access to code in other realms. For example, each frame in a browser has its own realm. And execution can jump from one frame to another, as the following HTML demonstrates.

<head><script>functiontest(arr){variframe=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);// falseconsole.log(arrinstanceofArray);// falseconsole.log(arrinstanceofiframe.Array);// true// But: symbols are the sameconsole.log(Symbol.iterator===iframe.Symbol.iterator);// true}</script></head><body><iframesrcdoc="<script>window.parent.test([])</script>"></iframe></body>

The problem is that each realm has its own global variables where each variableArray points to a different object, even though they are all essentially the same object. Similarly, libraries and user code are loaded once per realm and each realm has a different version of the same object.

Objects are compared by identity, but booleans, numbers and strings are compared by value. Therefore, no matter in which realm a number 123 originated, it is indistinguishable from all other 123s. That is similar to the number literal123 always producing the same value.

Symbols have individual identities and thus don’t travel across realms as smoothly as other primitive values. That is a problem for symbols such asSymbol.iterator that should work across realms: If an object is iterable in one realm, it should be iterable in all realms. All built-in symbols are managed by the JavaScript engine, which makes sure that, e.g.,Symbol.iterator is the same value in each realm. If a library wants to provide cross-realm symbols, it has to rely on extra support, which comes in the form of theglobal symbol registry: This registry is global to all realms and maps strings to symbols. For each symbol, the library needs to come up with a string that is as unique as possible. To create the symbol, it doesn’t useSymbol(), it asks the registry for the symbol that the string is mapped to. If the registry already has an entry for the string, the associated symbol is returned. Otherwise, entry and symbol are created first.

You ask the registry for a symbol viaSymbol.for() and retrieve the string associated with a symbol (itskey) viaSymbol.keyFor():

> const sym = Symbol.for('Hello everybody!');> Symbol.keyFor(sym)'Hello everybody!'

Cross-realm symbols, such asSymbol.iterator, that are provided by the JavaScript engine, are not in the registry:

> Symbol.keyFor(Symbol.iterator)undefined

7.8FAQ: symbols

7.8.1Can I use symbols to define private properties?

The original plan was for symbols to support private properties (there would have been public and private symbols). But that feature was dropped, because using “get” and “set” (two meta-object protocol operations) for managing private data does not interact well with proxies:

These two goals are at odds. The chapter on classes explainsyour options for managing private data. Symbols is one of these options, but you don’t get the same amount of safety that you’d get from private symbols, because it’s possible to determine the symbols used as an object’s property keys, viaObject.getOwnPropertySymbols() andReflect.ownKeys().

7.8.2Are symbols primitives or objects?

In some ways, symbols are like primitive values, in other ways, they are like objects:

What are symbols then – primitive values or objects? In the end, they were turned into primitives, for two reasons.

First, symbols are more like strings than like objects: They are a fundamental value of the language, they are immutable and they can be used as property keys. Symbols having unique identities doesn’t necessarily contradict them being like strings: UUID algorithms produce strings that are quasi-unique.

Second, symbols are most often used as property keys, so it makes sense to optimize the JavaScript specification and implementations for that use case. Then symbols don’t need many abilities of objects:

Symbols not having these abilities makes life easier for the specification and the implementations. The V8 team has also said that when it comes to property keys, it is easier to make a primitive type a special case than certain objects.

7.8.3Do we really need symbols? Aren’t strings enough?

In contrast to strings, symbols are unique and prevent name clashes. That is nice to have for tokens such as colors, but it is essential for supporting meta-level methods such as the one whose key isSymbol.iterator. Python uses the special name__iter__ to avoid clashes. You can reserve double underscore names for programming language mechanisms, but what is a library to do? With symbols, we have an extensibility mechanism that works for everyone. As you can see later, in the section on public symbols, JavaScript itself already makes ample use of this mechanism.

There is one hypothetical alternative to symbols when it comes to clash-free property keys: using a naming convention. For example, strings with URLs (e.g.'http://example.com/iterator'). But that would introduce a second category of property keys (versus “normal” property names that are usually valid identifiers and don’t contain colons, slashes, dots, etc.), which is basically what symbols are, anyway. Then we may just as well introduce a new kind of value.

7.8.4Are JavaScript’s symbols like Ruby’s symbols?

No, they are not.

Ruby’s symbols are basically literals for creating values. Mentioning the same symbol twice produces the same value twice:

:foo==:foo

The JavaScript functionSymbol() is a factory for symbols – each value it returns is unique:

Symbol('foo')!==Symbol('foo')

7.9The spelling of well-known symbols: whySymbol.iterator and notSymbol.ITERATOR (etc.)?

Well-known symbols are stored in properties whose names start with lowercase characters and are camel-cased. In a way, these properties are constants and it is customary for constants to have all-caps names (Math.PI etc.). But the reasoning for their spelling is different: Well-known symbols are used instead of normal property keys, which is why their “names” follow the rules for property keys, not the rules for constants.

7.10The symbol API

This section gives an overview of the ECMAScript 6 API for symbols.

7.10.1The functionSymbol

Symbol(description?) : symbol
Creates a new symbol. The optional parameterdescription allows you to give the symbol a description. The only way to access the description is to convert the symbol to a string (viatoString() orString()). The result of such a conversion is'Symbol('+description+')':

> const sym = Symbol('hello');> String(sym)'Symbol(hello)'

Symbol is can’t be used as a constructor – an exception is thrown if you invoke it vianew.

7.10.2Methods of symbols

The only useful method that symbols have istoString() (viaSymbol.prototype.toString()).

7.10.3Converting symbols to other values

Conversion toExplicit conversionCoercion (implicit conversion)
booleanBoolean(sym) → OK!sym → OK
numberNumber(sym)TypeErrorsym*2TypeError
stringString(sym) → OK''+symTypeError
 sym.toString() → OK`${sym}`TypeError
objectObject(sym) → OKObject.keys(sym) → OK

7.10.4Well-known symbols

The global objectSymbol has several properties that serve as constants for so-calledwell-known symbols. These symbols let you configure how ES6 treats an object, by using them as property keys. This is a list ofall well-known symbols:

7.10.5Global symbol registry

If you want a symbol to be the same in all realms, you need to use the global symbol registry, via the following two methods:

Next:8. Template literals

[8]ページ先頭

©2009-2025 Movatter.jp