Symbols

By specification, object property keys may be either of string type, or of symbol type. Not numbers, not booleans, only strings or symbols, these two types. Till now we’ve only seen strings. Now let’s see the advantages that symbols can give us.

“Symbol” value represents a unique identifier. A value of this type can be created using Symbol():

// id is a new symbol
let id = Symbol();

We can also give symbol 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 unique

Symbols are guaranteed to be unique. Even if we create many symbols with the same description, they are different values. The description is just a label that doesn’t affect anything.

let id1 = Symbol("id");
let id2 = Symbol("id");

alert(id1 == id2); // false

Symbols don’t auto-convert to a string

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

If we really want to show a symbol, we need to call .toString() on it, like here:

let id = Symbol("id");
alert(id.toString()); // Symbol(id), now it works

“Hidden” properties

Symbols allow us to create “hidden” properties of an object, that no other part of code can occasionally access or overwrite. For instance, if we want to store an “identifier” for the object user, we can use a symbol as a key for it:

let user = { name: "John" };
let id = Symbol("id");

user[id] = "ID Value";
alert( user[id] ); // we can access the data using the symbol as the key

But whats the benefit of using symbol instead of string properties like "id".

Imagine that another script wants to have its own “id” property inside user, for its own purposes. That may be another JavaScript library, so the scripts are completely unaware of each other. 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, because symbols are always different, even if they have the same name. Now note that 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 = "ID Value";

// ...if later another script the uses "id" for its purposes...

user.id = "Their id value"
// boom! overwritten! it did not mean to harm the colleague, but did it!

Symbols in a literal

If we want to use a symbol in an object literal, we need square brackets.

let id = Symbol("id");

let user = {
  name: "John",
  [id]: 123 // not just "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.

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] );

That’s a part of the general “hiding” concept. If another script or a library loops over our object, it won’t unexpectedly access a symbolic property.

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

Property keys of other types are coerced to strings

We can only use strings or symbols as keys in objects. Other types are converted to strings. For instance, a number 0 becomes a string "0" when used as a property key:

let obj = {
  0: "test" // same as "0": "test"
};

// both alerts access the same property (the number 0 is converted to string "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (same property)

Global Symbols

As we’ve seen, usually all symbols are different, even if they have the same names. 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 create or read a symbol in 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 and then return it.

// read from the global registry
let id = Symbol.for("id"); // if the symbol did not exist, it is created

// read it again
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.

Symbol.keyFor

For global symbols, not only Symbol.for(key) returns a symbol by name, but there’s a reverse call: Symbol.keyFor(sym), that does the reverse: returns a name by a global symbol.

let sym = Symbol.for("name");
let sym2 = Symbol.for("id");

// get name from 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 return undefined.

alert( Symbol.keyFor(Symbol.for("name")) ); // name, global symbol

alert( Symbol.keyFor(Symbol("name2")) ); // undefined, the argument isn't a global symbol

Get all Symbols

The Object.getOwnPropertySymbols() method returns an array of all symbol properties found directly upon a given object.

const object1 = {};
const a = Symbol('a');
const b = Symbol.for('b');

object1[a] = 'localSymbol';
object1[b] = 'globalSymbol';

const objectSymbols = Object.getOwnPropertySymbols(object1);

console.log(objectSymbols.length);

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:

For instance, Symbol.toPrimitive allows us to describe object to primitive conversion. We’ll see its use very soon.