javascript

examples

examples.js
// ============================================
// 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}`);
  }
}
Examples - JavaScript Tutorial | DeepML