The Ultimate Guide to JavaScript Symbol
Summary: in this tutorial, you will learn about the JavaScript symbol primitive type and how to use the symbol effectively.
Creating symbols
ES6 added Symbol
as a new primitive type. Unlike other primitive types such as number, boolean, null, undefined, and string, the symbol type doesn’t have a literal form.
To create a new symbol, you use the global Symbol()
function as shown in this example:
let s = Symbol('foo');
Code language: JavaScript (javascript)
The Symbol()
function creates a new unique value each time you call it:
console.log(Symbol() === Symbol()); // false
Code language: JavaScript (javascript)
The Symbol()
function accepts a description
as an optional argument. The description
argument will make your symbol more descriptive.
The following example creates two symbols: firstName
and lastName
.
let firstName = Symbol('first name'),
lastName = Symbol('last name');
Code language: JavaScript (javascript)
You can access the symbol’s description property using the toString()
method. The console.log()
method calls the toString()
method of the symbol implicitly as shown in the following example:
console.log(firstName); // Symbol(first name)
console.log(lastName); // Symbol(last name)
Code language: JavaScript (javascript)
Since symbols are primitive values, you can use the typeof
operator to check whether a variable is a symbol. ES6 extended typeof
to return the symbol
string when you pass in a symbol variable:
console.log(typeof firstName); // symbol
Code language: JavaScript (javascript)
Since a symbol is a primitive value, if you attempt to create a symbol using the new
operator, you will get an error:
let s = new Symbol(); // error
Code language: JavaScript (javascript)
Sharing symbols
ES6 provides you with a global symbol registry that allows you to share symbols globally. If you want to create a symbol that will be shared, you use the Symbol.for()
method instead of calling the Symbol()
function.
The Symbol.for()
method accepts a single parameter that can be used for symbol’s description, as shown in the following example:
let ssn = Symbol.for('ssn');
Code language: JavaScript (javascript)
The Symbol.for()
method first searches for the symbol with the ssn
key in the global symbol registry. It returns the existing symbol if there is one. Otherwise, the Symbol.for()
method creates a new symbol, registers it to the global symbol registry with the specified key, and returns the symbol.
Later, if you call the Symbol.for()
method using the same key, the Symbol.for()
method will return the existing symbol.
let citizenID = Symbol.for('ssn');
console.log(ssn === citizenID); // true
Code language: JavaScript (javascript)
In this example, we used the Symbol.for()
method to look up the symbol with the ssn
key. Since the global symbol registry already contained it, the Symbol.for()
method returned the existing symbol.
To get the key associated with a symbol, you use the Symbol.keyFor()
method as shown in the following example:
console.log(Symbol.keyFor(citizenID)); // 'ssn'
Code language: JavaScript (javascript)
If a symbol that does not exist in the global symbol registry, the System.keyFor()
method returns undefined
.
let systemID = Symbol('sys');
console.log(Symbol.keyFor(systemID)); // undefined
Code language: JavaScript (javascript)
Symbol usages
A) Using symbols as unique values
Whenever you use a string or a number in your code, you should use symbols instead. For example, you have to manage the status in the task management application.
Before ES6, you would use strings such as open
, in progress
, completed
, canceled
, and on hold
to represent different statuses of a task. In ES6, you can use symbols as follows:
let statuses = {
OPEN: Symbol('Open'),
IN_PROGRESS: Symbol('In progress'),
COMPLETED: Symbol('Completed'),
HOLD: Symbol('On hold'),
CANCELED: Symbol('Canceled')
};
// complete a task
task.setStatus(statuses.COMPLETED);
Code language: JavaScript (javascript)
B) Using symbol as the computed property name of an object
You can use symbols as computed property names. See the following example:
let status = Symbol('status');
let task = {
[status]: statuses.OPEN,
description: 'Learn ES6 Symbol'
};
console.log(task);
Code language: JavaScript (javascript)
To get all the enumerable properties of an object, you use the Object.keys()
method.
console.log(Object.keys(task)); // ["description"]
Code language: JavaScript (javascript)
To get all properties of an object whether the properties are enumerable or not, you use the Object.getOwnPropertyNames()
method.
console.log(Object.getOwnPropertyNames(task)); // ["description"]
Code language: JavaScript (javascript)
To get all property symbols of an object, you use the Object.getOwnPropertySymbols()
method, which has been added in ES6.
console.log(Object.getOwnPropertySymbols(task)); //[Symbol(status)]
Code language: JavaScript (javascript)
The Object.getOwnPropertySymbols()
method returns an array of own property symbols from an object.
Well-known symbols
ES6 provides predefined symbols which are called well-known symbols. The well-known symbols represent the common behaviors in JavaScript. Each well-known symbol is a static property of the Symbol
object.
Symbol.hasInstance
The Symbol.hasInstance
is a symbol that changes the behavior of the instanceof
operator. Typically, when you use the instanceof
operator:
obj instanceof type;
Code language: JavaScript (javascript)
JavaScript will call the Symbol.hasIntance
method as follows:
type[Symbol.hasInstance](obj);
Code language: JavaScript (javascript)
It then depends on the method to determine if obj
is an instance of the type
object. See the following example.
class Stack {
}
console.log([] instanceof Stack); // false
Code language: JavaScript (javascript)
The []
array is not an instance of the Stack
class, therefore, the instanceof
operator returns false
in this example.
Assuming that you want the []
array is an instance of the Stack
class, you can add the Symbol.hasInstance
method as follows:
class Stack {
static [Symbol.hasInstance](obj) {
return Array.isArray(obj);
}
}
console.log([] instanceof Stack); // true
Code language: JavaScript (javascript)
Symbol.iterator
The Symbol.iterator
specifies whether a function will return an iterator for an object.
The objects that have Symbol.iterator
property are called iterable objects.
In ES6, all collection objects (Array, Set and Map) and strings are iterable objects.
ES6 provides the for…of loop that works with the iterable object as in the following example.
var numbers = [1, 2, 3];
for (let num of numbers) {
console.log(num);
}// 1
// 2
// 3
Code language: JavaScript (javascript)
Internally, JavaScript engine first calls the Symbol.iterator
method of the numbers
array to get the iterator object. Then, it calls the iterator.next()
method and copies the value property fo the iterator object into the num
variable. After three iterations, the done
property of the result object is true
, the loop exits.
You can access the default iterator object via System.iterator
symbol as follows:
var iterator = numbers[Symbol.iterator]();console.log(iterator.next()); // Object {value: 1, done: false}
console.log(iterator.next()); // Object {value: 2, done: false}
console.log(iterator.next()); // Object {value: 3, done: false}
console.log(iterator.next()); // Object {value: undefined, done: true}
Code language: JavaScript (javascript)
By default, a collection is not iterable. However, you can make it iterable by using the Symbol.iterator
as shown in the following example:
class List {
constructor() {
this.elements = [];
} add(element) {
this.elements.push(element);
return this;
}
*[Symbol.iterator]() {
for (let element of this.elements) {
yield element;
}
}
}
let chars = new List();
chars.add('A')
.add('B')
.add('C');
// because of the Symbol.iterator
for (let c of chars) {
console.log(c);
}
// A
// B
// C
Code language: JavaScript (javascript)
Symbol.isConcatSpreadable
To concatenate two arrays, you use the concat()
method as shown in the following example:
let odd = [1, 3],
even = [2, 4];
let all = odd.concat(even);
console.log(all); // [1, 3, 2, 4]
Code language: JavaScript (javascript)
In this example, the resulting array contains the single elements of both arrays. In addition, the concat()
method also accepts a non-array argument as illustrated below.
let extras = all.concat(5);
console.log(extras); // [1, 3, 2, 4, 5]
Code language: JavaScript (javascript)
The number 5 becomes the fifth element of the array.
As you see in the above example, when we pass an array to the concat()
method, the concat()
method spreads the array into individual elements. However, it treats a single primitive argument differently. Prior to ES6, you could not change this behavior.
This is why the Symbol.isConcatSpreadable
symbol comes into play.
The Symbol.isConcatSpreadable
property is a Boolean value that determines whether an object is added individually to the result of the concat()
function.
Consider the following example:
let list = {
0: 'JavaScript',
1: 'Symbol',
length: 2
};
let message = ['Learning'].concat(list);
console.log(message); // ["Learning", Object]
Code language: JavaScript (javascript)
The list object is concatenated to the ['Learning']
array. However, its individual elements are not spreaded.
To enable the elements of the list
object added to the array individually when passing to the concat()
method, you need to add the Symbol.isConcatSpreadable
property to the list
object as follows:
let list = {
0: 'JavaScript',
1: 'Symbol',
length: 2,
[Symbol.isConcatSpreadable]: true
};
let message = ['Learning'].concat(list);
console.log(message); // ["Learning", "JavaScript", "Symbol"]
Code language: JavaScript (javascript)
Note that if you set the value of the Symbol.isConcatSpreadable
to false
and pass the list
object to the concat()
method, it will be concatenated to the array as the whole object.
Symbol.toPrimitive
The Symbol.toPrimitive
method determines what should happen when an object is converted into a primitive value.
The JavaScript engine defines the Symbol.toPrimitive
method on the prototype of each standard type.
The Symbol.toPrimitive
method takes a hint
argument which has one of three values: “number”, “string”, and “default”. The hint
argument specifies the type of the return value. The hint
parameter is filled by the JavaScript engine based on the context in which the object is used.
Here is an example of using the Symbol.toPrimitive
method.
function Money(amount, currency) {
this.amount = amount;
this.currency = currency;
}
Money.prototype[Symbol.toPrimitive] = function(hint) {
var result;
switch (hint) {
case 'string':
result = this.amount + this.currency;
break;
case 'number':
result = this.amount;
break;
case 'default':
result = this.amount + this.currency;
break;
}
return result;
}var price = new Money(799, 'USD');
console.log('Price is ' + price); // Price is 799USD
console.log(+price + 1); // 800
console.log(String(price)); // 799USD
Code language: JavaScript (javascript)
In this tutorial, you have learned about JavaScript symbols and how to use symbols for unique values and object properties. Also, you learned how to use well-known symbols to modify object behaviors.