The symbol is a new primitive value introduced by ES6. It aims to provide us with unique identifiers. In this article, we explore how it works, in what way it is used in the JavaScript language and how we can benefit from that.
Creating symbols
To create a symbol, we use its constructor.
1 |
const symbol = Symbol(); |
The function has an optional string parameter that acts as a description.
1 2 |
const symbol = Symbol('description'); console.log(symbol); |
Symbol(description)
The significant thing is that even if you use the same description twice, every symbol is unique.
1 |
Symbol('description') === Symbol('description'); |
false
The symbol is a new primitive value and has its own type. We can check it using typeof:
1 |
typeof Symbol(); |
symbol
Converting the symbol type
In the “Looking into assembly code of coercion” article, we explore how we can convert types in Javascript. A big part of it is the implicit conversion that happens when we use values of different types together:
1 |
console.log(1 + " added to " + 2 + " equals " + 3); |
1 added to 2 equals 3
Even though other types often converse under the hood, it does not include the symbol type.
1 2 |
const symbol = Symbol('Hello'); symbol + 'world!'; |
Uncaught TypeError: Cannot convert a Symbol value to a string
If you would want to use symbols in such a way, converse them explicitly or use the description property.
1 2 3 |
const symbol = Symbol('Hello'); console.log(`${symbol.description} world!`); |
Hello world!
How to use symbols
Before the introduction of symbols, the object keys could only be strings. Trying to use an object as the key for a property does not give us an expected result.
1 2 3 4 5 6 |
const key = {}; const myObject = { [key]: 'Hello world!' }; console.log(myObject); |
123 {[object Object]: 'Hello world!'}
The things are different with symbols. The ECMAScript specification states that we can use them as keys:
property
part of an object that associates a key (either a String value or a Symbol value) and a value
Let’s try to do it!
1 2 3 4 5 6 |
const key = Symbol(); const myObject = { [key]: 'Hello world!' }; console.log(myObject); |
123 {Symbol(): 'Hello world!'}
Even if two symbols have the same description, they still don’t collide with each other when we use them as keys:
1 2 3 4 5 6 |
const key = Symbol('key'); const myObject = { [key]: 'Hello world!' }; console.log(myObject[key] === myObject[Symbol('key')]); |
false
It means that we can assign an unlimited number of unique symbols and don’t worry about them evert conflicting with each other.
Accessing the value
Now, the only way to access our value is to use the symbol.
1 |
console.log(myObject[key]); // Hello world! |
There are certain differences when it comes to iterating through the properties of an object with symbols. The Object.keys, Object.entries, and Object.entries functions don’t give us access to any values that use symbols – the same goes for a for...in loop. The way to iterate through them is to use the Object.getOwnPropertySymbols function.
1 2 3 4 5 6 7 8 9 |
const key = Symbol(); const myObject = { [key]: 'Hello world!' }; Object.getOwnPropertySymbols(myObject) .forEach((symbol) => { console.log(myObject[symbol]); }); |
Hello world!
Judging by the above, we can conclude that symbols provide some hidden layer under in the object, separate from keys that are strings.
Symbols are unique… most of the time
The way to create a global, not-unique symbol is to use the Symbol.for function. You need to provide it with a string – it searches the symbol registry for a symbol associated with the given key and returns it if found. If that’s not the case, it creates a new symbol.
1 2 |
const symbol = Symbol.for('key'); console.log(symbol === Symbol.for('key')); |
true
If you have doubts about a symbol being unique, you can use the Symbol.keyFor function. It returns the associated key if found.
1 2 3 |
Symbol.keyFor( Symbol.for('key') ); |
key
1 2 3 |
Symbol.keyFor( Symbol('key') ); |
undefined
Well-known symbols
With the introduction of symbols through the ES6, the language itself incorporates it into some of its mechanisms.
Symbol.toStringTag
With the Symbol.toStringTag we can change the internal [[Class]] property of an object that is used when stringifying an object.
1 2 3 4 5 |
const dog = { name: 'Fluffy' } console.log(dog.toString()) // '[object Object]' dog[Symbol.toStringTag] = 'Dog'; console.log(dog.toString()) // '[object Dog]' |
If you would like to know more, look into the Looking into assembly code of coercion for additional information
Symbol.iterator
The for...of loop makes use of the symbols under the hood. Wit the use of the Symbol.iterator, ic accesses the values to iterate over.
1 2 3 4 5 |
const array = ['a', 'b', 'c']; for(let value of array) { console.log(value); } |
1 2 3 4 5 6 7 8 9 10 |
const array = ['a', 'b', 'c']; let iterator = array[Symbol.iterator](); let iteration; while( !(iteration = iterator.next()).done ){ console.log(iteration.value); } |
a
b
c
Knowing this, you can override the default value that Symbol.iterator holds to change the behavior of the for...of loop.
1 2 3 4 5 6 7 8 9 10 11 12 |
const array = [1, 2, 3]; array[Symbol.iterator] = function* () { let i = 0; while(this[i] !== undefined) { yield Math.random(); ++i; } } for(let value of array) { console.log(value); } |
0.0523720769432241
0.9489775095218018
0.9041067477874434
The above example uses generators. If you would like to know more about them, check out Demystifying generators. Implementing async/await
Other internal symbols
There are many different symbols that you can use to alter the mechanisms of the language. If you are interested in a complete list, check out the MDN documentation.
Summary
In this article, we’ve covered the Symbol type. There are quite a few ways to use that knowledge: by exploring how symbols work we’ve learned how to modify the JavaScript mechanisms through some of the well-known symbols. By using symbols, we can prevent values from colliding with each other, for example in a way that it happened with the MooTools library: if you need to modify the built-in prototypes like the String and Array, it might be a good idea to use Symbols for that. Since symbols are quite an interesting part of the JavaScript language, it is worth trying out.