Categories
JavaScript Basics

Introduction to JavaScript Symbols

Spread the love

In ES2015, a new primitive type called Symbol is introduced. It is a unique and immutable identifier. Once you have created it, it cannot be copied.

Every time you create a new symbol, it’s a unique one. Symbols are mainly used for unique identifiers in an object. That’s a symbol’s only purpose.

There are also special symbols that we can use to implement various operations or override the default behavior of some operations.


Defining Symbols

There are some static properties and methods of its own that expose the global symbol registry. It is like a built-in object, but it doesn’t have a constructor, so we can’t write new Symbol to construct a symbol object with the new keyword.

To create new symbols, we can write:

const fooSymbol = Symbol('foo')

Note that each time we call the Symbol function, we get a new symbol, so if we write:

Symbol('sym') === Symbol('sym')

The expression above would be false. This is because every symbol is unique.


Built-In Symbols

There are built-in Symbols that are used as identifiers for various methods and values. Methods with some symbols are called when some operators are being used.

Symbol.hasInstance

Symbol.hasInstance is a method that checks if an object is an instance of a given constructor. This method is called when the instanceof operator is invoked.

We can override the Symbol.hasInstance method as follows:

class Foo {
  static [Symbol.hasInstance](instance) {
    return typeof instance.foo != 'undefined';
  }
}
console.log({ foo: 'abc' } instanceof Foo);

In the code above, we defined that an object is an instance of the Foo class if there’s a value for the foo property. Therefore, { foo: ‘abc’ } instanceof Foo should return true since it has the foo property set to 'abc'.

Symbol.isConcatSpreadable

Symbol.isConcatSpreadable is a boolean value that indicates whether an object should be flattened in an array by the array concat method.

We can use it as in the following code:

The first console.log should output:

["a", "b", "c", true, false]

And the second one should output:

["a", "b", "c", Array(2)]

This is because before the second concat call, we set arr2[Symbol.isConcatSpreadable] to false, which prevents the content of arr2 from being spread into the new array that’s returned by the concat method.

Symbol.iterator

This is a method that’s called when we want to return an iterator for the spread operator or the for...of loop. It’s called when the for...of loop is run.

For example, given that we have the following code:

const obj = {
  0: 1,
  1: 2,
  2: 3
};
console.log(obj[0]);

If you try to loop through an array with the for...of loop or the forEach function, or try to use the spread operator with it, the example with the obj object will result in an error since it’s not an iterable object.

We can make it iterable by adding a generator function with the Symbol Symbol.iterator to it like in the following code:

Then, when we iterate the obj object with the for...of loop like the code below:

for (let num of obj) {
  console.log(num);
}

We get back the entries of the new obj object that we made iterable.

The spread operator would also work. If we have the following code:

console.log([...obj]);

We get [1, 2, 3] from the console.log output.

Symbol.match

A boolean property that’s part of a regular expression instance that replaced the matched substring of a string. It’s called by the string’s replace method.

For example, we can use it to let us call the startsWith and endsWith methods with the regular expression strings:

const regexpFoo = /foo/;
regexpFoo[Symbol.match] = false;
console.log('/foo/'.startsWith(regexpFoo));
console.log('/baz/'.endsWith(regexpFoo));

The important part is that we set regexpFoo[Symbol.match] to false, which indicates that the string we called startsWith and endsWith with aren’t regular expression objects since the isRegExp check will indicate that the '/foo/' and '/baz/' strings aren’t regular expression objects.

Otherwise, they’ll be considered regular expression objects even though they’re strings and we’ll get the following error:

Uncaught TypeError: First argument to String.prototype.startsWith must not be a regular expression

Symbol.replace

A regular expression method that replaces matched substrings of a string. Called by the String.prototype.replace method.

We can create our own replace method for our object as follows by using the Symbol.replace symbol as an identifier for the method:

The Replacer class has a constructor that takes a value that can be used to replace the current string instance.

When we run the last line, we should get ‘bar’ since string has the value 'foo' and we call the replace method to replace itself with whatever we passed into the constructor of Replacer.

Symbol.search

A regular expression method that returns the index within a string that matches the regular expression. Called by the String.prototype.search method.

For example, we can implement our own Symbol.search method as we do in the following code:

In the code above, our Symbol.search method looks up if the string, which is the string that we call search on, has whatever we pass into the this.value field which we assign when we call the constructor.

Therefore, we get true from the console.log output since 'bar' is in ‘foobar'. On the other hand, if we call:

console.log('foobar'.search(new Searcher('baz')));

Then we get the value false since ‘baz’ isn’t in 'foobar'.

Symbol.species

A property that has a function as its value that is the constructor function which is used to create derived objects.

Symbol.split

A method that’s part of the regular expression object that splits a string according to the indexes that match the regular expression. It’s called by the string’s split method.

Symbol.toPrimitive

A method that converts an object to a corresponding primitive value. It’s called when the + unary operator is used or converting an object to a primitive string.

For example, we can write our own Symbol.toPrimitive method to convert various values to a primitive value:

Then we get:

10
hello
true
false

From the console.log statements at the bottom of our code.

Symbol.toString

A method that returns a string representation of an object. It’s called whenever an object’s toString method is called.

Symbol.unscopables

An object whose own property names are property names that are excluded from the with environment bindings of the associated objects.


Conclusion

Symbols are a new type of data that was introduced with ES6. They are used to identify the properties of an object. They’re immutable and every instance is considered different, even though they may have the same content.

We can implement various methods identified by special symbols to implement certain operations like instanceof, converting objects to primitive values, and searching for substrings, in our own code.

Leave a Reply

Your email address will not be published.

If you like the content of this blog, subscribe to my email list to get exclusive articles not available to anyone else.