Problem Statement
What is unique about Symbol?
Explanation
Every Symbol is guaranteed to be unique, even if created with the same description.
Symbols are primitive values used as unique property keys. They prevent property name collisions.
Symbol properties are not enumerable in for in or Object.keys. They are hidden from normal iteration.
Use Symbol.for to create global symbols that can be retrieved later.
Symbols are commonly used for private-like properties and defining well-known symbols.
Understanding Symbols shows advanced JavaScript knowledge of primitive types and metaprogramming.
Code Solution
SolutionRead Only
// SYMBOL UNIQUENESS
const sym1 = Symbol('description');
const sym2 = Symbol('description');
console.log(sym1 === sym2); // false (always unique!)
// Each Symbol is unique
const id1 = Symbol('id');
const id2 = Symbol('id');
const user = {
[id1]: 123,
[id2]: 456
};
console.log(user[id1]); // 123
console.log(user[id2]); // 456
// Different properties!
// HIDDEN PROPERTIES
const obj = {
name: 'John',
age: 30
};
const secretId = Symbol('id');
obj[secretId] = 12345;
// Not in for...in
for (let key in obj) {
console.log(key); // Only 'name' and 'age'
}
// Not in Object.keys
console.log(Object.keys(obj)); // ['name', 'age']
// Not in JSON.stringify
console.log(JSON.stringify(obj)); // {"name":"John","age":30}
// But accessible
console.log(obj[secretId]); // 12345
// Get symbols
const symbols = Object.getOwnPropertySymbols(obj);
console.log(symbols); // [Symbol(id)]
// GLOBAL SYMBOLS
// Create or get global symbol
const globalSym1 = Symbol.for('app.id');
const globalSym2 = Symbol.for('app.id');
console.log(globalSym1 === globalSym2); // true (same symbol!)
// Get key from symbol
console.log(Symbol.keyFor(globalSym1)); // 'app.id'
// WELL-KNOWN SYMBOLS
// Symbol.iterator - make object iterable
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) {
return {value: current++, done: false};
} else {
return {done: true};
}
}
};
}
};
for (let num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
// Symbol.toStringTag - customize toString
const myObject = {
[Symbol.toStringTag]: 'MyCustomClass'
};
console.log(myObject.toString()); // '[object MyCustomClass]'
// Symbol.hasInstance - customize instanceof
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([1, 2] instanceof MyArray); // true
// PRACTICAL USE CASES
// Private properties (before # syntax)
const _private = Symbol('private');
class BankAccount {
constructor(balance) {
this[_private] = balance;
}
getBalance() {
return this[_private];
}
}
const account = new BankAccount(1000);
console.log(account.getBalance()); // 1000
// console.log(account._private); // undefined (hidden)
// Unique constants
const COLOR_RED = Symbol('red');
const COLOR_GREEN = Symbol('green');
const COLOR_BLUE = Symbol('blue');
function getColor(color) {
switch(color) {
case COLOR_RED:
return '#FF0000';
case COLOR_GREEN:
return '#00FF00';
case COLOR_BLUE:
return '#0000FF';
}
}
// Metadata storage
const metadata = Symbol('metadata');
function addMetadata(obj, data) {
obj[metadata] = data;
}
const item = {name: 'Product'};
addMetadata(item, {created: Date.now()});
console.log(item[metadata]); // {created: ...}
console.log(Object.keys(item)); // ['name'] (metadata hidden)