By specification, only two primitive types may serve as object property keys:
- string type, or
- symbol type.
Otherwise, if one uses another type, such as number, itâs autoconverted to string. So that obj[1] is the same as obj["1"], and obj[true] is the same as obj["true"].
Until now weâve been using only strings.
Now letâs explore symbols, see what they can do for us.
Symbols
A âsymbolâ represents a unique identifier.
A value of this type can be created using Symbol():
let id = Symbol();
Upon creation, we can give symbols a description (also called a symbol name), mostly useful for debugging purposes:
// id is a symbol with the description "id"
let id = Symbol("id");
Symbols are guaranteed to be unique. Even if we create many symbols with exactly the same description, they are different values. The description is just a label that doesnât affect anything.
For instance, here are two symbols with the same description â they are not equal:
let id1 = Symbol("id");
let id2 = Symbol("id");
alert(id1 == id2); // false
If you are familiar with Ruby or another language that also has some sort of âsymbolsâ â please donât be misguided. JavaScript symbols are different.
So, to summarize, a symbol is a âprimitive unique valueâ with an optional description. Letâs see where we can use them.
Most values in JavaScript support implicit conversion to a string. For instance, we can alert almost any value, and it will work. Symbols are special. They donât auto-convert.
For instance, this alert will show an error:
let id = Symbol("id");
alert(id); // TypeError: Cannot convert a Symbol value to a string
Thatâs a âlanguage guardâ against messing up, because strings and symbols are fundamentally different and should not accidentally convert one into another.
If we really want to show a symbol, we need to explicitly call .toString() on it, like here:
let id = Symbol("id");
alert(id.toString()); // Symbol(id), now it works
Or get symbol.description property to show the description only:
let id = Symbol("id");
alert(id.description); // id
âHiddenâ properties
Symbols allow us to create âhiddenâ properties of an object, that no other part of code can accidentally access or overwrite.
For instance, if weâre working with user objects, that belong to a third-party code. Weâd like to add identifiers to them.
Letâs use a symbol key for it:
let user = { // belongs to another code
name: "John"
};
let id = Symbol("id");
user[id] = 1;
alert( user[id] ); // we can access the data using the symbol as the key
Whatâs the benefit of using Symbol("id") over a string "id"?
As user objects belong to another codebase, itâs unsafe to add fields to them, since we might affect pre-defined behavior in that other codebase. However, symbols cannot be accessed accidentally. The third-party code wonât be aware of newly defined symbols, so itâs safe to add symbols to the user objects.
Also, imagine that another script wants to have its own identifier inside user, for its own purposes.
Then that script can create its own Symbol("id"), like this:
// ...
let id = Symbol("id");
user[id] = "Their id value";
There will be no conflict between our and their identifiers, because symbols are always different, even if they have the same name.
â¦But if we used a string "id" instead of a symbol for the same purpose, then there would be a conflict:
let user = { name: "John" };
// Our script uses "id" property
user.id = "Our id value";
// ...Another script also wants "id" for its purposes...
user.id = "Their id value"
// Boom! overwritten by another script!
Symbols in an object literal
If we want to use a symbol in an object literal {...}, we need square brackets around it.
Like this:
let id = Symbol("id");
let user = {
name: "John",
[id]: 123 // not "id": 123
};
Thatâs because we need the value from the variable id as the key, not the string âidâ.
Symbols are skipped by forâ¦in
Symbolic properties do not participate in for..in loop.
For instance:
let id = Symbol("id");
let user = {
name: "John",
age: 30,
[id]: 123
};
for (let key in user) alert(key); // name, age (no symbols)
// the direct access by the symbol works
alert( "Direct: " + user[id] ); // Direct: 123
Object.keys(user) also ignores them. Thatâs a part of the general âhiding symbolic propertiesâ principle. If another script or a library loops over our object, it wonât unexpectedly access a symbolic property.
In contrast, Object.assign copies both string and symbol properties:
let id = Symbol("id");
let user = {
[id]: 123
};
let clone = Object.assign({}, user);
alert( clone[id] ); // 123
Thereâs no paradox here. Thatâs by design. The idea is that when we clone an object or merge objects, we usually want all properties to be copied (including symbols like id).
Global symbols
As weâve seen, usually all symbols are different, even if they have the same name. But sometimes we want same-named symbols to be same entities. For instance, different parts of our application want to access symbol "id" meaning exactly the same property.
To achieve that, there exists a global symbol registry. We can create symbols in it and access them later, and it guarantees that repeated accesses by the same name return exactly the same symbol.
In order to read (create if absent) a symbol from the registry, use Symbol.for(key).
That call checks the global registry, and if thereâs a symbol described as key, then returns it, otherwise creates a new symbol Symbol(key) and stores it in the registry by the given key.
For instance:
// read from the global registry
let id = Symbol.for("id"); // if the symbol did not exist, it is created
// read it again (maybe from another part of the code)
let idAgain = Symbol.for("id");
// the same symbol
alert( id === idAgain ); // true
Symbols inside the registry are called global symbols. If we want an application-wide symbol, accessible everywhere in the code â thatâs what they are for.
In some programming languages, like Ruby, thereâs a single symbol per name.
In JavaScript, as we can see, thatâs true for global symbols.
Symbol.keyFor
We have seen that for global symbols, Symbol.for(key) returns a symbol by name. To do the opposite â return a name by global symbol â we can use: Symbol.keyFor(sym):
For instance:
// get symbol by name
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");
// get name by symbol
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
The Symbol.keyFor internally uses the global symbol registry to look up the key for the symbol. So it doesnât work for non-global symbols. If the symbol is not global, it wonât be able to find it and returns undefined.
That said, all symbols have the description property.
For instance:
let globalSymbol = Symbol.for("name");
let localSymbol = Symbol("name");
alert( Symbol.keyFor(globalSymbol) ); // name, global symbol
alert( Symbol.keyFor(localSymbol) ); // undefined, not global
alert( localSymbol.description ); // name
System symbols
There exist many âsystemâ symbols that JavaScript uses internally, and we can use them to fine-tune various aspects of our objects.
They are listed in the specification in the Well-known symbols table:
Symbol.hasInstanceSymbol.isConcatSpreadableSymbol.iteratorSymbol.toPrimitive- â¦and so on.
For instance, Symbol.toPrimitive allows us to describe object to primitive conversion. Weâll see its use very soon.
Other symbols will also become familiar when we study the corresponding language features.
Summary
Symbol is a primitive type for unique identifiers.
Symbols are created with Symbol() call with an optional description (name).
Symbols are always different values, even if they have the same name. If we want same-named symbols to be equal, then we should use the global registry: Symbol.for(key) returns (creates if needed) a global symbol with key as the name. Multiple calls of Symbol.for with the same key return exactly the same symbol.
Symbols have two main use cases:
-
âHiddenâ object properties.
If we want to add a property into an object that âbelongsâ to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in
for..in, so it wonât be accidentally processed together with other properties. Also it wonât be accessed directly, because another script does not have our symbol. So the property will be protected from accidental use or overwrite.So we can âcovertlyâ hide something into objects that we need, but others should not see, using symbolic properties.
-
There are many system symbols used by JavaScript which are accessible as
Symbol.*. We can use them to alter some built-in behaviors. For instance, later in the tutorial weâll useSymbol.iteratorfor iterables,Symbol.toPrimitiveto setup object-to-primitive conversion and so on.
Technically, symbols are not 100% hidden. There is a built-in method Object.getOwnPropertySymbols(obj) that allows us to get all symbols. Also there is a method named Reflect.ownKeys(obj) that returns all keys of an object including symbolic ones. But most libraries, built-in functions and syntax constructs donât use these methods.

Comments
<code>tag, for several lines â wrap them in<pre>tag, for more than 10 lines â use a sandbox (plnkr, jsbin, codepenâ¦)