Learning Objectives
- Understand Symbol primitive type
- Create unique identifiers
- Use well-known symbols
- Implement private properties
- Apply symbol best practices
What are Symbols?
Symbols are a primitive data type introduced in ES6 for creating unique identifiers. Every symbol is guaranteed to be unique, making them perfect for property keys that won't collide with other properties.
Creating Symbols
// Create unique symbol
const id = Symbol();
const id2 = Symbol();
console.log(id === id2); // false - each is unique
// With description (for debugging)
const userId = Symbol('user id');
console.log(userId.toString()); // 'Symbol(user id)'
console.log(userId.description); // 'user id'
// Use as object property
const user = {
name: 'John',
[userId]: 123
};
console.log(user[userId]); // 123
console.log(user.name); // 'John'
Private Properties
const _balance = Symbol('balance');
const _transactions = Symbol('transactions');
class BankAccount {
constructor(initialBalance) {
this[_balance] = initialBalance;
this[_transactions] = [];
}
deposit(amount) {
this[_balance] += amount;
this[_transactions].push({ type: 'deposit', amount });
}
getBalance() {
return this[_balance];
}
getTransactions() {
return [...this[_transactions]];
}
}
const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
console.log(account[_balance]); // undefined (not accessible)
console.log(Object.keys(account)); // [] (symbols not enumerable)
Well-Known Symbols
Symbol.iterator
const collection = {
items: ['a', 'b', 'c'],
[Symbol.iterator]() {
let index = 0;
return {
next: () => ({
value: this.items[index++],
done: index > this.items.length
})
};
}
};
// Now iterable
for (const item of collection) {
console.log(item); // 'a', 'b', 'c'
}
// Works with spread
console.log([...collection]); // ['a', 'b', 'c']
Symbol.toStringTag
class MyClass {
get [Symbol.toStringTag]() {
return 'MyClass';
}
}
const obj = new MyClass();
console.log(Object.prototype.toString.call(obj)); // [object MyClass]
console.log(obj.toString()); // [object MyClass]
Symbol.hasInstance
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // true
console.log({} instanceof MyArray); // false
Global Symbol Registry
// Create/retrieve global symbol
const globalSym = Symbol.for('app.id');
const sameSym = Symbol.for('app.id');
console.log(globalSym === sameSym); // true
// Get key for symbol
console.log(Symbol.keyFor(globalSym)); // 'app.id'
// Regular symbols not in registry
const localSym = Symbol('local');
console.log(Symbol.keyFor(localSym)); // undefined
Accessing Symbol Properties
const sym1 = Symbol('sym1');
const sym2 = Symbol('sym2');
const obj = {
name: 'John',
[sym1]: 'value1',
[sym2]: 'value2'
};
// Symbols not in Object.keys()
console.log(Object.keys(obj)); // ['name']
// Get symbol properties
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(sym1), Symbol(sym2)]
// Get all property keys
console.log(Reflect.ownKeys(obj)); // ['name', Symbol(sym1), Symbol(sym2)]
Best Practices
1. Use for truly unique identifiers
const ID = Symbol('id');
const user = { [ID]: 123, name: 'John' };
2. Implement private properties
const _private = Symbol('private');
class MyClass {
constructor() {
this[_private] = 'secret';
}
}
3. Use Symbol.for() for cross-realm symbols
const shared = Symbol.for('shared.key');
Key Takeaways
- Symbols are unique, immutable primitives
- Perfect for creating private properties
- Not enumerable in for...in or Object.keys()
- Well-known symbols enable metaprogramming
- Symbol.for() creates global symbols
- Each Symbol() call creates a unique value
- Use Object.getOwnPropertySymbols() to access
- Symbol properties don't conflict with string keys