Well-known Symbols

I was recently asking on public forums if people can name at least three Well-known Symbols in JavaScript without looking up and few could, which gave birth to this post.

A well-known symbol is a non-registered symbol that's the same across realms. If we were to list the well-known symbols, the would be:

Let us see some examples to get an idea of usefulness.

Symbol.iterator: This symbol is used to define the default iterator for an object. It is used to enable iteration over objects in a for-of loop or with the spread operator.

const obj = { a: 1, b: 2, c: 3 };

obj[Symbol.iterator] = function*() {
for (const key of Object.keys(this)) {
yield [key, this[key]];
}
};

for (const [key, value] of obj) {
console.log(`${key}: ${value}`);
}

Symbol.toStringTag: This symbol is used to specify the string value returned by the Object.prototype.toString method when called on an object. It can be used to provide a custom string representation for objects.

class MyClass {
static [Symbol.toStringTag] = 'MyClass';
}

const myInstance = new MyClass();

console.log(myInstance.toString()); // outputs '[object MyClass]'

Symbol.toPrimitive: This symbol is used to specify the behavior of the valueOf and toString methods for an object, when called implicitly. It can be used to provide custom string and numeric representations for objects.

class Life {
valueOf() {
return 42;
}

[Symbol.toPrimitive](hint) {
switch (hint) {
case "number":
return this.valueOf();
case "string":
return "Forty Two";
case "default":
return true;
}
}
}

const myLife = new Life();
console.log(+myLife); // 42
console.log(`${myLife}`); // "Forty Two"
console.log(myLife + 0); // 42

Symbol.asyncIterator: This symbol is used to define an asynchronous iterator for an object. It is used to enable asynchronous iteration over objects.

class MyAsyncIterable {
async *[Symbol.asyncIterator]() {
for (let i = 0; i < 5; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
}
}

(async () => {
for await (const value of new MyAsyncIterable()) {
console.log(value);
}
})();

// Output after one second:
// 0
// Output after two seconds:
// 1
// Output after three seconds:
// 2
// Output after four seconds:
// 3
// Output after five seconds:
// 4

Symbol.hasInstance: This symbol is used to determine if an object is an instance of a constructor. It can be used to change the behavior of the instanceof operator.

class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}

const arr = [1, 2, 3];
console.log(arr instanceof MyArray); // true

Symbol.isConcatSpreadable: This symbol is used to determine if an object should be flattened when concatenated with other objects. It can be used to change the behavior of the Array.prototype.concat method.

const arr1 = [1, 2, 3];
const spreadable = { [Symbol.isConcatSpreadable]: true, 0: 4, 1: 5, 2: 6, length: 3 };

console.log([].concat(arr1, spreadable)); // [1, 2, 3, 4, 5, 6]

Symbol.species: This symbol is used to specify the constructor to use when creating derived objects. It can be used to customize the behavior of built-in methods that create new objects.

class MyArray extends Array {
static get [Symbol.species]() {
return Array;
}
}

const myArray = new MyArray(1, 2, 3);
const mappedArray = myArray.map(x => x * 2);

console.log(mappedArray instanceof MyArray); // false
console.log(mappedArray instanceof Array); // true

P.S: this functionality may be removed in the future.

Symbol.match: This symbol is used to determine the value to search for when using the String.prototype.match method. It can be used to change the behavior of the match method for RegExp-like objects.

const myRegex = /test/;
'/test/'.startsWith(myRegex); // Throws TypeError

const re = /foo/;
re[Symbol.match] = false;
"/foo/".startsWith(re); // true
"/bar/".endsWith(re); // false

P.S: The presence of that symbol is what marks an object as "being a regex".

const myRegex = /foo/g;
const str = 'How many foos in the the foo foo bar?';

for (result of myRegex[Symbol.matchAll](str)) {
console.log(result); // we will get the matches
}

Symbol.replace: This symbol is used to determine the replacement value when using the String.prototype.replace method. It can be used to change the behavior of the replace method for RegExp-like objects.

const customReplace = str => str.replace(/\d+/g, match => `-${match}-`);

const customString = {
[Symbol.replace]: customReplace
};

const originalString = "foo123bar456baz";

const result = originalString.replace(customString, '*');

console.log(result); // outputs "foo-123-bar-456-baz"

Symbol.search: This symbol is used to determine the value to search for when using the String.prototype.search method. It can be used to change the behavior of the search method for RegExp-like objects.

const customSearch = str => str.search(/\d+/);

const customRegExp = {
[Symbol.search]: customSearch
};

const string = "foo123bar456baz";

string.search(customRegExp); // outputs 3

Symbol.split: This symbol is used to determine the value to split when using the String.prototype.split method. It can be used to change the behavior of the split method for RegExp-like objects.

const { Symbol } = require('es6-symbol');

const customSplit = str => str.split(/\d+/);

const customRegExp = {
[Symbol.split]: customSplit
};

const string = "foo123bar456baz";

string.split(customRegExp); // outputs [ 'foo', 'bar', 'baz' ]

Symbol.unscopables: This symbol is used to determine the properties of an object that should be excluded from the scope of with statements. It can be used to change the behavior of with statements.

const person = {
age: 42
};

person[Symbol.unscopables] = {
age: true
};

with (person) {
console.log(age);
// Expected output: Error: age is not defined
}

Symbol.dispose: Explicit Resource Management refers to a system in which the user manages the lifespan of a "resource" explicitly, either by using imperative methods (such as Symbol.dispose) or declarative methods (such as using a block-scoped declaration).

{ 
console.log(1);
using {
[Symbol.dispose]() {
console.log(2);
}
};
console.log(3);
}
// will log 1, 3, 2

This informational blog aimed to impart a deeper understanding of the well-known symbols intrinsic to the JavaScript language. These symbols, such as Symbol.iterator, Symbol.toStringTag, and Symbol.for, represent sophisticated and versatile instruments that can be utilized to augment and regulate the behavior of code. Acquiring a comprehensive understanding of the available symbols within the JavaScript environment is crucial in the development of high-performing, maintainable, and scalable applications. Hence, it is recommended that during the conceptualization and implementation phase of a project, one evaluates the feasibility of incorporating these symbols, to streamline the code and achieve desired outcomes in a more sophisticated and elegant manner.

Thanks to @ljharb for a quick review on this post.

Feel free to share this article. You may as well ping me on Twitter.

Published