Skip to content
Plurafy

Symbol and Well-Known Symbols

Using Symbols for unique identifiers and metaprogramming

You need to create unique, non-enumerable property keys that won’t conflict with other properties. You also need to customize how objects behave with built-in operations like iteration.

Symbols are primitive values that are unique and non-enumerable. Well-known symbols like Symbol.iterator customize built-in JavaScript behavior.

// Create unique symbols
const id = Symbol('id');
const name = Symbol('name');
// Each symbol is unique
console.log(id === id); // true
console.log(Symbol('id') === Symbol('id')); // false
// Use as object keys
const obj = {
[id]: 123,
[name]: 'Alice'
};
console.log(obj[id]); // 123
// Symbols are hidden from regular iteration
for (const key in obj) {
console.log(key); // Nothing (symbols not enumerated)
}
// But accessible with Object.getOwnPropertySymbols
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(id), Symbol(name)]
const _password = Symbol('password');
const _salt = Symbol('salt');
class User {
constructor(username, password) {
this.username = username;
this[_password] = password;
this[_salt] = Math.random();
}
verifyPassword(guess) {
return this[_password] === guess;
}
// Private methods
static #authenticate(user, pwd) {
return user[_password] === pwd;
}
}
const user = new User('john', 'secret123');
console.log(user.username); // 'john'
console.log(user[_password]); // 'secret123'
// Attempt to find password
console.log(Object.keys(user)); // ['username']
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(password), Symbol(salt)]
// Symbol.iterator - make object iterable
const range = {
start: 1,
end: 5,
[Symbol.iterator]() {
let current = this.start;
return {
next: () => {
if (current <= this.end) {
return { value: current++, done: false };
}
return { done: true };
}
};
}
};
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
// Symbol.toStringTag - customize Object.prototype.toString
class MyClass {
get [Symbol.toStringTag]() {
return 'MyClass';
}
}
console.log(Object.prototype.toString.call(new MyClass()));
// [object MyClass]
// Symbol.hasInstance - customize instanceof
class MyArray {
static [Symbol.hasInstance](obj) {
return Array.isArray(obj);
}
}
console.log([] instanceof MyArray); // true
// Symbol.toPrimitive - customize type coercion
class Money {
constructor(amount) {
this.amount = amount;
}
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return this.amount;
}
return `$${this.amount}`;
}
}
const money = new Money(100);
console.log(money + 50); // 150
console.log(String(money)); // '$100'
// Global symbol registry
const globalSymbol = Symbol.for('mySymbol');
const sameSymbol = Symbol.for('mySymbol');
console.log(globalSymbol === sameSymbol); // true
// Retrieve key from symbol
console.log(Symbol.keyFor(globalSymbol)); // 'mySymbol'
// Useful for cross-realm/cross-module access
const Status = (() => {
const _status = Symbol('status');
return Object.freeze({
ACTIVE: { [_status]: 'active' },
INACTIVE: { [_status]: 'inactive' },
PENDING: { [_status]: 'pending' }
});
})();
// Type-safe enum
const currentStatus = Status.ACTIVE;
console.log(currentStatus); // { Symbol(status): 'active' }
// Impossible to accidentally create invalid status
// Status.INVALID won't exist
// Private data
const _data = Symbol('data');
class Container {
constructor(data) {
this[_data] = data;
}
}
// Metadata
const metadata = new Map();
metadata.set(Symbol.for('author'), 'John');
// Customizable behavior
class Logger {
[Symbol.for('log')]() {
console.log('Logging...');
}
}
// Extension points
const plugin = {
[Symbol.for('initialize')]() {
console.log('Plugin initialized');
}
};