JavaScript - JavaScript Symbols and Well-Known Symbols
1. Introduction
JavaScript provides several built-in data types for storing and manipulating information. Most developers are familiar with strings, numbers, booleans, objects, and arrays. However, JavaScript also includes a special primitive data type called Symbol.
A Symbol is used to create unique identifiers. Unlike strings or numbers, every newly created Symbol is unique, even when two Symbols have the same description.
Symbols are particularly useful when working with object properties, custom iteration, metaprogramming, and advanced JavaScript libraries.
This chapter explains JavaScript Symbols and Well-Known Symbols in a detailed, student-friendly manner.
2. What Is a Symbol in JavaScript?
A Symbol is a primitive data type introduced in ECMAScript 2015 (ES6).
A Symbol represents a unique value that can be used as an identifier, especially as a property key in an object.
Syntax
JavaScript
const symbolName = Symbol();
You can also provide a description:
JavaScript
const symbolName = Symbol("description");
The description helps developers understand the purpose of the Symbol. It does not determine its identity.
Example
JavaScript
const firstSymbol = Symbol("student");
const secondSymbol = Symbol("student");
console.log(firstSymbol === secondSymbol);
Output:
false
Explanation
Although both Symbols have the description "student", they are different values.
-
firstSymbolcontains one unique Symbol. -
secondSymbolcontains another unique Symbol. -
The equality operator returns
false.
This is the fundamental property of Symbols.
3. Why Are Symbols Needed?
Before Symbols were introduced, developers commonly used strings as object property names.
Consider the following example:
JavaScript
const student = {
name: "Rahul",
age: 21
};
console.log(student.name);
Here, name and age are string-based property keys.
In larger applications, different developers or libraries may use the same property name. This can lead to accidental overwriting.
Problem with String Property Names
JavaScript
const student = {};
student.id = 101;
student.id = 202;
console.log(student.id);
Output:
202
The second assignment replaces the first value.
Using Symbols to Avoid Property Name Collisions
JavaScript
const student = {};
const firstId = Symbol("id");
const secondId = Symbol("id");
student[firstId] = 101;
student[secondId] = 202;
console.log(student[firstId]);
console.log(student[secondId]);
Output:
101
202
Explanation
The two Symbols represent separate property keys.
Even though both descriptions are "id", they do not conflict.
This is useful when:
-
Developing reusable libraries.
-
Adding internal properties to objects.
-
Avoiding conflicts between independent modules.
-
Creating custom JavaScript frameworks.
-
Defining special object behavior.
4. Creating Symbols
There are different ways to create and use Symbols in JavaScript.
4.1 Creating an Empty Symbol
JavaScript
const mySymbol = Symbol();
console.log(typeof mySymbol);
Output:
symbol
The typeof operator returns "symbol".
4.2 Creating a Symbol with a Description
JavaScript
const userSymbol = Symbol("user");
console.log(userSymbol.description);
Output:
user
The description property provides the description supplied during creation.
4.3 Symbols with the Same Description
JavaScript
const symbolA = Symbol("test");
const symbolB = Symbol("test");
console.log(symbolA === symbolB);
Output:
false
Descriptions are for identification and debugging. They do not make Symbols equal.
4.4 Symbols Cannot Be Created Using new
The Symbol constructor is not used with the new keyword.
Incorrect:
JavaScript
const mySymbol = new Symbol("test");
This throws a TypeError.
Correct:
JavaScript
const mySymbol = Symbol("test");
The Symbol() function creates a primitive Symbol value directly.
5. Using Symbols as Object Property Keys
One of the most important uses of Symbols is creating unique object property keys.
Example
JavaScript
const nameKey = Symbol("name");
const student = {
[nameKey]: "Anita",
age: 20
};
console.log(student[nameKey]);
Output:
Anita
Explanation
The square brackets are important:
JavaScript
[nameKey]
They tell JavaScript to use the value stored in nameKey as the property key.
Without square brackets:
JavaScript
const student = {
nameKey: "Anita"
};
The property name would be the literal string "nameKey" rather than the Symbol stored in the variable.
Adding a Symbol Property After Object Creation
JavaScript
const student = {};
const rollNumber = Symbol("rollNumber");
student[rollNumber] = 45;
console.log(student[rollNumber]);
Output:
45
Accessing Symbol Properties
You can access Symbol properties using bracket notation:
JavaScript
const key = Symbol("score");
const result = {};
result[key] = 95;
console.log(result[key]);
Output:
95
Dot notation does not work with a Symbol variable:
JavaScript
console.log(result.key);
This looks for a property named "key", not the Symbol stored in key.
6. Symbols and Object Property Collisions
Symbols are useful when multiple parts of an application add properties to the same object.
Example Without Symbols
JavaScript
const employee = {
name: "Kiran"
};
employee.status = "Active";
employee.status = "Inactive";
console.log(employee.status);
Output:
Inactive
The second property assignment overwrites the first.
Example with Symbols
JavaScript
const employee = {
name: "Kiran"
};
const departmentStatus = Symbol("status");
const accountStatus = Symbol("status");
employee[departmentStatus] = "Active";
employee[accountStatus] = "Inactive";
console.log(employee[departmentStatus]);
console.log(employee[accountStatus]);
Output:
Active
Inactive
The two properties are independent.
Practical Use
Suppose a library adds an internal property to a user object:
JavaScript
const internalKey = Symbol("internal");
const user = {
name: "Ravi"
};
user[internalKey] = {
loggedIn: true
};
Another library can use its own Symbol without accidentally overwriting the first library's property.
7. Symbol Properties Are Not Normally Included in Common Enumeration
Symbol properties behave differently from ordinary string properties.
Consider:
JavaScript
const id = Symbol("id");
const student = {
name: "Meena",
age: 22,
[id]: 101
};
console.log(Object.keys(student));
Output:
["name", "age"]
Explanation
Object.keys() returns enumerable own string-keyed properties. It does not include Symbol-keyed properties.
The Symbol property still exists.
JavaScript
console.log(student[id]);
Output:
101
Checking Whether a Symbol Property Exists
JavaScript
console.log(Object.getOwnPropertySymbols(student));
Output:
[Symbol(id)]
Object.getOwnPropertySymbols() returns an array of the object's own Symbol property keys.
Important Difference
|
Method
|
Returns Symbol properties?
|
| --- | --- |
|
Object.keys()
|
No
|
|
Object.values()
|
No
|
|
Object.entries()
|
No
|
|
for...in
|
No
|
|
Object.getOwnPropertySymbols()
|
Yes
|
|
Reflect.ownKeys()
|
Yes, along with string keys
|
Symbol properties are not completely invisible. They are simply excluded from many ordinary string-key enumeration methods.
8. Symbol Properties and for...in
The for...in loop iterates over enumerable string-keyed properties.
Example
JavaScript
const secretKey = Symbol("secret");
const user = {
name: "Arun",
age: 25,
[secretKey]: "Private Data"
};
for (const key in user) {
console.log(key);
}
Output:
name
age
The Symbol property is not displayed.
However, you can retrieve it separately:
JavaScript
console.log(user[secretKey]);
Output:
Private Data
Important Note
Symbols are not a security mechanism. A Symbol-keyed property can still be discovered using reflection methods if the Symbol key is accessible or obtained from the object.
9. The Global Symbol Registry
JavaScript provides a global Symbol registry through:
JavaScript
Symbol.for()
This method creates or retrieves a shared Symbol associated with a string key.
Syntax
JavaScript
Symbol.for("key");
Example
JavaScript
const first = Symbol.for("user");
const second = Symbol.for("user");
console.log(first === second);
Output:
true
Explanation
When Symbol.for("user") is called:
-
JavaScript checks whether a Symbol is already registered under
"user". -
If one exists, it returns that Symbol.
-
Otherwise, it creates a new Symbol and registers it.
Therefore, both variables refer to the same Symbol.
Difference Between Symbol() and Symbol.for()
JavaScript
const a = Symbol("user");
const b = Symbol("user");
console.log(a === b);
Output:
false
Using the registry:
JavaScript
const c = Symbol.for("user");
const d = Symbol.for("user");
console.log(c === d);
Output:
true
Retrieving the Registry Key
Use:
JavaScript
Symbol.keyFor()
Example:
JavaScript
const registeredSymbol = Symbol.for("employee");
console.log(Symbol.keyFor(registeredSymbol));
Output:
employee
Important Difference
JavaScript
const localSymbol = Symbol("employee");
console.log(Symbol.keyFor(localSymbol));
Output:
undefined
Only registered Symbols created through Symbol.for() have a retrievable registry key.
10. What Are Well-Known Symbols?
Well-Known Symbols are built-in Symbol values provided by JavaScript.
They allow developers to customize how objects behave with built-in language operations.
For example, they can control:
-
How an object is iterated.
-
How an object is converted to a primitive value.
-
How an object behaves with
instanceof. -
How an object responds to certain operators.
-
How objects work with built-in methods such as
Array.from().
Well-Known Symbols are accessed through the Symbol object.
Example
JavaScript
Symbol.iterator
This is a built-in Symbol used to define the default iterator for an object.
Another example:
JavaScript
Symbol.toPrimitive
This is used to customize primitive conversion.
These Symbols are standardized parts of JavaScript.
11. Important Well-Known Symbols
The following are important Well-Known Symbols for JavaScript development.
|
Well-Known Symbol
|
Purpose
|
| --- | --- |
|
Symbol.iterator
|
Defines the default iterator for an object
|
|
Symbol.asyncIterator
|
Defines the default asynchronous iterator
|
|
Symbol.toPrimitive
|
Controls conversion of an object to a primitive
|
|
Symbol.toStringTag
|
Customizes the tag returned by Object.prototype.toString
|
|
Symbol.hasInstance
|
Customizes the behavior of the instanceof operator
|
|
Symbol.isConcatSpreadable
|
Controls whether an object is spread by Array.prototype.concat()
|
|
Symbol.species
|
Controls the constructor used by certain derived objects
|
|
Symbol.match
|
Defines matching behavior for String.prototype.match()
|
|
Symbol.replace
|
Defines replacement behavior for String.prototype.replace()
|
|
Symbol.search
|
Defines search behavior for String.prototype.search()
|
|
Symbol.split
|
Defines splitting behavior for String.prototype.split()
|
|
Symbol.matchAll
|
Defines behavior for String.prototype.matchAll()
|
|
Symbol.unscopables
|
Controls which properties are excluded from with environment lookup
|
The most commonly studied Well-Known Symbols are Symbol.iterator, Symbol.toPrimitive, Symbol.toStringTag, and Symbol.hasInstance.
12. Symbol.iterator
Symbol.iterator is used to define an object's default iterator.
Objects that are iterable can be used with:
-
for...of -
Spread syntax (
...) -
Array.from() -
Other APIs that consume iterables
Arrays and strings already have built-in iterators.
Example with an Array
JavaScript
const numbers = [10, 20, 30];
for (const number of numbers) {
console.log(number);
}
Output:
10
20
30
The array is iterable because it provides a method under Symbol.iterator.
Checking the Iterator
JavaScript
const numbers = [10, 20, 30];
const iterator = numbers[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
Output:
{ value: 10, done: false }
{ value: 20, done: false }
{ value: 30, done: false }
{ value: undefined, done: true }
Explanation
The iterator's next() method returns an object with two properties:
-
value: The current value. -
done: Indicates whether iteration has completed.
When done becomes true, iteration is finished.
13. Creating a Custom Iterable Object
You can use Symbol.iterator to make your own object work with for...of.
Example
JavaScript
const student = {
name: "Anita",
age: 21,
*[Symbol.iterator]() {
yield this.name;
yield this.age;
}
};
for (const value of student) {
console.log(value);
}
Output:
Anita
21
Explanation
The object contains a generator method assigned to Symbol.iterator.
JavaScript
*[Symbol.iterator]()
This method produces values one at a time using yield.
The for...of loop automatically uses the iterator.
Important Point
An ordinary object is not iterable by default:
JavaScript
const student = {
name: "Anita",
age: 21
};
for (const value of student) {
console.log(value);
}
This causes a TypeError because