javascript
examples
examples.js⚡javascript
// ============================================
// 17.3 Symbols - Examples
// ============================================
// --------------------------------------------
// 1. Creating Symbols
// --------------------------------------------
// Basic symbol creation
const sym1 = Symbol();
const sym2 = Symbol();
console.log(sym1 === sym2); // false - each symbol is unique
console.log(typeof sym1); // 'symbol'
// Symbols with descriptions
const idSymbol = Symbol('id');
const nameSymbol = Symbol('name');
console.log(idSymbol.description); // 'id'
console.log(nameSymbol.description); // 'name'
// Same description, still unique
const symA = Symbol('mySymbol');
const symB = Symbol('mySymbol');
console.log(symA === symB); // false
// Cannot use 'new' with Symbol
// const sym = new Symbol(); // TypeError!
// --------------------------------------------
// 2. Symbols as Object Keys
// --------------------------------------------
const ID = Symbol('id');
const SECRET = Symbol('secret');
const user = {
name: 'Alice',
age: 30,
[ID]: 12345,
[SECRET]: 'hidden value',
};
console.log(user.name); // 'Alice'
console.log(user[ID]); // 12345
console.log(user[SECRET]); // 'hidden value'
// Symbol keys are not enumerable
console.log(Object.keys(user)); // ['name', 'age']
console.log(Object.values(user)); // ['Alice', 30]
console.log(JSON.stringify(user)); // '{"name":"Alice","age":30}'
// Access symbol keys specifically
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id), Symbol(secret)]
// Get all keys including symbols
console.log(Reflect.ownKeys(user)); // ['name', 'age', Symbol(id), Symbol(secret)]
// --------------------------------------------
// 3. Global Symbol Registry
// --------------------------------------------
// Symbol.for() creates or retrieves global symbols
const globalSym1 = Symbol.for('app.userId');
const globalSym2 = Symbol.for('app.userId');
console.log(globalSym1 === globalSym2); // true - same symbol!
// Symbol.keyFor() retrieves the key
console.log(Symbol.keyFor(globalSym1)); // 'app.userId'
// Local symbols are not in registry
const localSym = Symbol('local');
console.log(Symbol.keyFor(localSym)); // undefined
// Cross-realm symbol sharing
// Symbol.for() works across iframes, workers, etc.
// --------------------------------------------
// 4. Hiding Properties with Symbols
// --------------------------------------------
const privateData = Symbol('private');
class BankAccount {
constructor(owner, balance) {
this.owner = owner;
this[privateData] = { balance, pin: '1234' };
}
getBalance() {
return this[privateData].balance;
}
deposit(amount) {
this[privateData].balance += amount;
}
}
const account = new BankAccount('Alice', 1000);
console.log(account.owner); // 'Alice'
console.log(account.getBalance()); // 1000
// Private data hidden from normal access
console.log(Object.keys(account)); // ['owner']
console.log(JSON.stringify(account)); // '{"owner":"Alice"}'
// But still accessible if you have the symbol
console.log(account[privateData]); // { balance: 1000, pin: '1234' }
// --------------------------------------------
// 5. Symbol.iterator - Custom Iteration
// --------------------------------------------
const range = {
start: 1,
end: 5,
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { done: true };
},
};
},
};
console.log([...range]); // [1, 2, 3, 4, 5]
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
// --------------------------------------------
// 6. Symbol.toStringTag
// --------------------------------------------
class Collection {
get [Symbol.toStringTag]() {
return 'Collection';
}
}
const col = new Collection();
console.log(Object.prototype.toString.call(col)); // '[object Collection]'
console.log(col.toString()); // '[object Collection]'
// Built-in examples
console.log(Object.prototype.toString.call(new Map())); // '[object Map]'
console.log(Object.prototype.toString.call(new Set())); // '[object Set]'
console.log(Object.prototype.toString.call(new Promise(() => {}))); // '[object Promise]'
// --------------------------------------------
// 7. Symbol.hasInstance
// --------------------------------------------
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // true
console.log([1, 2, 3] instanceof MyArray); // true
console.log({} instanceof MyArray); // false
// Custom type checking
class EvenNumber {
static [Symbol.hasInstance](value) {
return typeof value === 'number' && value % 2 === 0;
}
}
console.log(4 instanceof EvenNumber); // true
console.log(5 instanceof EvenNumber); // false
console.log(10 instanceof EvenNumber); // true
// --------------------------------------------
// 8. Symbol.toPrimitive
// --------------------------------------------
const money = {
amount: 100,
currency: 'USD',
[Symbol.toPrimitive](hint) {
console.log(`Hint: ${hint}`);
switch (hint) {
case 'number':
return this.amount;
case 'string':
return `${this.currency} ${this.amount}`;
default: // 'default'
return this.amount;
}
},
};
console.log(+money); // Hint: number, 100
console.log(`${money}`); // Hint: string, 'USD 100'
console.log(money + 50); // Hint: default, 150
console.log(money == 100); // Hint: default, true
// --------------------------------------------
// 9. Symbol.isConcatSpreadable
// --------------------------------------------
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr2[Symbol.isConcatSpreadable] = false;
console.log(arr1.concat(arr2)); // [1, 2, 3, [4, 5, 6]]
// Make object spreadable
const arrayLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.isConcatSpreadable]: true,
};
console.log(['x'].concat(arrayLike)); // ['x', 'a', 'b', 'c']
// --------------------------------------------
// 10. Symbol.species
// --------------------------------------------
class MyArray extends Array {
static get [Symbol.species]() {
return Array; // Methods return plain Array, not MyArray
}
}
const myArr = new MyArray(1, 2, 3);
const mapped = myArr.map((x) => x * 2);
console.log(myArr instanceof MyArray); // true
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
// --------------------------------------------
// 11. Using Symbols for Constants
// --------------------------------------------
const Status = {
PENDING: Symbol('pending'),
APPROVED: Symbol('approved'),
REJECTED: Symbol('rejected'),
};
function processRequest(status) {
switch (status) {
case Status.PENDING:
return 'Waiting for review';
case Status.APPROVED:
return 'Request approved';
case Status.REJECTED:
return 'Request rejected';
default:
return 'Unknown status';
}
}
console.log(processRequest(Status.PENDING)); // 'Waiting for review'
console.log(processRequest(Status.APPROVED)); // 'Request approved'
// Can't accidentally match with strings
console.log(processRequest('pending')); // 'Unknown status'
// --------------------------------------------
// 12. Symbols for Metaprogramming
// --------------------------------------------
const INIT = Symbol('init');
const VALIDATE = Symbol('validate');
class FormField {
constructor(value) {
this.value = value;
this[INIT]();
}
[INIT]() {
console.log('Initializing field...');
this.touched = false;
this.valid = this[VALIDATE]();
}
[VALIDATE]() {
return this.value !== null && this.value !== undefined;
}
setValue(value) {
this.value = value;
this.touched = true;
this.valid = this[VALIDATE]();
}
}
const field = new FormField('test');
console.log(field.valid); // true
field.setValue(null);
console.log(field.valid); // false
// --------------------------------------------
// 13. Symbol Polyfill Pattern
// --------------------------------------------
// Before Symbol, unique keys were hard
const oldStyle = {
__privateId__: 123, // Could be overwritten
};
// With Symbol, truly unique
const PRIVATE_ID = Symbol('privateId');
const newStyle = {
[PRIVATE_ID]: 123, // Cannot be accidentally overwritten
};
// --------------------------------------------
// 14. Plugin System with Symbols
// --------------------------------------------
const HOOKS = Symbol('hooks');
class PluginSystem {
constructor() {
this[HOOKS] = {};
}
addHook(name, callback) {
if (!this[HOOKS][name]) {
this[HOOKS][name] = [];
}
this[HOOKS][name].push(callback);
}
runHooks(name, data) {
const hooks = this[HOOKS][name] || [];
return hooks.reduce((result, hook) => hook(result), data);
}
}
const system = new PluginSystem();
system.addHook('transform', (data) => data.toUpperCase());
system.addHook('transform', (data) => `[${data}]`);
console.log(system.runHooks('transform', 'hello')); // '[HELLO]'
// --------------------------------------------
// 15. Checking for Symbol Support
// --------------------------------------------
// Feature detection
if (typeof Symbol !== 'undefined') {
console.log('Symbols are supported');
}
// Check for specific well-known symbols
if (Symbol.iterator) {
console.log('Iteration protocol is supported');
}
// Get all well-known symbols on Symbol
console.log('Well-known symbols:');
for (const key of Object.getOwnPropertyNames(Symbol)) {
if (typeof Symbol[key] === 'symbol') {
console.log(` Symbol.${key}`);
}
}