javascript
exercises
exercises.js⚡javascript
// ============================================
// 17.3 Symbols - Exercises
// ============================================
// Exercise 1: Create Unique Symbols
// Create two symbols with the same description
// Verify they are not equal
// Your code here:
// const sym1 = ...
// const sym2 = ...
// console.log(sym1 === sym2); // Should be false
/*
Solution:
const sym1 = Symbol('test');
const sym2 = Symbol('test');
console.log(sym1 === sym2); // false
console.log(sym1.description); // 'test'
console.log(sym2.description); // 'test'
*/
// --------------------------------------------
// Exercise 2: Symbol as Object Key
// Create an object with a regular property and a symbol property
// The symbol property should store a secret ID
// Your code here:
// const SECRET_ID = Symbol('secretId');
// const user = {
// name: 'Alice',
// // Add symbol property
// };
/*
Solution:
const SECRET_ID = Symbol('secretId');
const user = {
name: 'Alice',
[SECRET_ID]: 'user_12345_secret'
};
console.log(user.name); // 'Alice'
console.log(user[SECRET_ID]); // 'user_12345_secret'
console.log(Object.keys(user)); // ['name']
*/
// --------------------------------------------
// Exercise 3: Global Symbol Registry
// Create two global symbols with the same key
// Verify they are the same symbol
// Your code here:
// const globalA = Symbol.for(...);
// const globalB = Symbol.for(...);
/*
Solution:
const globalA = Symbol.for('shared.key');
const globalB = Symbol.for('shared.key');
console.log(globalA === globalB); // true
console.log(Symbol.keyFor(globalA)); // 'shared.key'
*/
// --------------------------------------------
// Exercise 4: Hide Private Data
// Create a class that uses a symbol to store private balance
// Provide public methods to get and update the balance
// Your code here:
// const BALANCE = Symbol('balance');
//
// class Wallet {
// constructor(initialBalance) {
// ...
// }
//
// getBalance() {
// ...
// }
//
// add(amount) {
// ...
// }
// }
/*
Solution:
const BALANCE = Symbol('balance');
class Wallet {
constructor(initialBalance) {
this[BALANCE] = initialBalance;
}
getBalance() {
return this[BALANCE];
}
add(amount) {
if (amount > 0) {
this[BALANCE] += amount;
}
}
spend(amount) {
if (amount > 0 && amount <= this[BALANCE]) {
this[BALANCE] -= amount;
return true;
}
return false;
}
}
const wallet = new Wallet(100);
console.log(wallet.getBalance()); // 100
wallet.add(50);
console.log(wallet.getBalance()); // 150
console.log(Object.keys(wallet)); // [] - BALANCE is hidden
*/
// --------------------------------------------
// Exercise 5: Implement Symbol.iterator
// Create an object that can be iterated using for...of
// It should yield numbers from min to max
// Your code here:
// const counter = {
// min: 1,
// max: 5,
// [Symbol.iterator]() {
// ...
// }
// };
//
// for (const num of counter) {
// console.log(num); // 1, 2, 3, 4, 5
// }
/*
Solution:
const counter = {
min: 1,
max: 5,
[Symbol.iterator]() {
let current = this.min;
const max = this.max;
return {
next() {
if (current <= max) {
return { value: current++, done: false };
}
return { done: true };
}
};
}
};
for (const num of counter) {
console.log(num); // 1, 2, 3, 4, 5
}
console.log([...counter]); // [1, 2, 3, 4, 5]
*/
// --------------------------------------------
// Exercise 6: Custom toStringTag
// Create a class with a custom [Symbol.toStringTag]
// so Object.prototype.toString.call() returns '[object MyClass]'
// Your code here:
// class MyClass {
// ...
// }
//
// const obj = new MyClass();
// console.log(Object.prototype.toString.call(obj)); // '[object MyClass]'
/*
Solution:
class MyClass {
get [Symbol.toStringTag]() {
return 'MyClass';
}
}
const obj = new MyClass();
console.log(Object.prototype.toString.call(obj)); // '[object MyClass]'
*/
// --------------------------------------------
// Exercise 7: Custom hasInstance
// Create a class where instanceof checks if a value is a positive number
// Your code here:
// class PositiveNumber {
// static [Symbol.hasInstance](value) {
// ...
// }
// }
//
// console.log(5 instanceof PositiveNumber); // true
// console.log(-3 instanceof PositiveNumber); // false
/*
Solution:
class PositiveNumber {
static [Symbol.hasInstance](value) {
return typeof value === 'number' && value > 0;
}
}
console.log(5 instanceof PositiveNumber); // true
console.log(-3 instanceof PositiveNumber); // false
console.log(0 instanceof PositiveNumber); // false
console.log('5' instanceof PositiveNumber); // false
*/
// --------------------------------------------
// Exercise 8: Custom toPrimitive
// Create an object that converts to different values based on hint
// Your code here:
// const temperature = {
// celsius: 25,
// fahrenheit: 77,
// [Symbol.toPrimitive](hint) {
// ...
// }
// };
//
// console.log(+temperature); // 25 (number)
// console.log(`${temperature}`); // '25°C' (string)
/*
Solution:
const temperature = {
celsius: 25,
fahrenheit: 77,
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return this.celsius;
case 'string':
return `${this.celsius}°C`;
default:
return this.celsius;
}
}
};
console.log(+temperature); // 25
console.log(`${temperature}`); // '25°C'
console.log(temperature + 5); // 30
*/
// --------------------------------------------
// Exercise 9: Status Constants with Symbols
// Create a set of status constants using symbols
// Create a function that returns a message based on status
// Your code here:
// const OrderStatus = {
// PENDING: Symbol('pending'),
// SHIPPED: Symbol('shipped'),
// DELIVERED: Symbol('delivered')
// };
//
// function getStatusMessage(status) {
// ...
// }
/*
Solution:
const OrderStatus = {
PENDING: Symbol('pending'),
SHIPPED: Symbol('shipped'),
DELIVERED: Symbol('delivered'),
CANCELLED: Symbol('cancelled')
};
function getStatusMessage(status) {
switch (status) {
case OrderStatus.PENDING:
return 'Your order is being processed';
case OrderStatus.SHIPPED:
return 'Your order is on the way';
case OrderStatus.DELIVERED:
return 'Your order has been delivered';
case OrderStatus.CANCELLED:
return 'Your order was cancelled';
default:
return 'Unknown status';
}
}
console.log(getStatusMessage(OrderStatus.PENDING)); // 'Your order is being processed'
console.log(getStatusMessage(OrderStatus.SHIPPED)); // 'Your order is on the way'
console.log(getStatusMessage('pending')); // 'Unknown status' - strings don't match
*/
// --------------------------------------------
// Exercise 10: Get All Properties Including Symbols
// Create a function that returns all property keys (strings and symbols)
// Your code here:
// function getAllKeys(obj) {
// ...
// }
const testObj = {
name: 'Test',
value: 42,
[Symbol('hidden')]: 'secret',
};
// console.log(getAllKeys(testObj));
// Should include: 'name', 'value', and Symbol(hidden)
/*
Solution:
function getAllKeys(obj) {
return [
...Object.keys(obj),
...Object.getOwnPropertySymbols(obj)
];
// Or simply: return Reflect.ownKeys(obj);
}
const SYM = Symbol('hidden');
const testObj = {
name: 'Test',
value: 42,
[SYM]: 'secret'
};
console.log(getAllKeys(testObj)); // ['name', 'value', Symbol(hidden)]
*/
// --------------------------------------------
// Exercise 11: Symbol-based Event System
// Create a simple event emitter using symbols for event types
// Your code here:
// const EVENTS = Symbol('events');
//
// class EventEmitter {
// constructor() {
// this[EVENTS] = {};
// }
//
// on(event, callback) {
// ...
// }
//
// emit(event, data) {
// ...
// }
// }
/*
Solution:
const EVENTS = Symbol('events');
class EventEmitter {
constructor() {
this[EVENTS] = {};
}
on(event, callback) {
if (!this[EVENTS][event]) {
this[EVENTS][event] = [];
}
this[EVENTS][event].push(callback);
}
emit(event, data) {
const callbacks = this[EVENTS][event] || [];
callbacks.forEach(cb => cb(data));
}
off(event, callback) {
if (!this[EVENTS][event]) return;
this[EVENTS][event] = this[EVENTS][event].filter(cb => cb !== callback);
}
}
const emitter = new EventEmitter();
emitter.on('message', data => console.log('Received:', data));
emitter.emit('message', 'Hello!'); // Received: Hello!
// Events are hidden
console.log(Object.keys(emitter)); // []
*/
// --------------------------------------------
// Exercise 12: Reverse Iterator
// Create an array subclass that iterates in reverse by default
// Your code here:
// class ReverseArray extends Array {
// [Symbol.iterator]() {
// ...
// }
// }
/*
Solution:
class ReverseArray extends Array {
[Symbol.iterator]() {
let index = this.length - 1;
const arr = this;
return {
next() {
if (index >= 0) {
return { value: arr[index--], done: false };
}
return { done: true };
}
};
}
}
const reversed = new ReverseArray(1, 2, 3, 4, 5);
console.log([...reversed]); // [5, 4, 3, 2, 1]
for (const item of reversed) {
console.log(item); // 5, 4, 3, 2, 1
}
*/
// --------------------------------------------
// Exercise 13: Type Checking with Symbol
// Create utility symbols and functions for type checking
// Your code here:
// Create symbols for custom types and a typeOf function
/*
Solution:
const TYPE = Symbol('type');
class Email {
static [Symbol.hasInstance](value) {
return typeof value === 'string' && value.includes('@');
}
get [Symbol.toStringTag]() {
return 'Email';
}
}
class PhoneNumber {
static [Symbol.hasInstance](value) {
return typeof value === 'string' && /^\d{10}$/.test(value);
}
}
console.log('test@example.com' instanceof Email); // true
console.log('not-email' instanceof Email); // false
console.log('1234567890' instanceof PhoneNumber); // true
console.log('123' instanceof PhoneNumber); // false
*/
// --------------------------------------------
// Exercise 14: Linked List with Symbol Iterator
// Create a linked list that's iterable
// Your code here:
// class LinkedList {
// ...
// [Symbol.iterator]() {
// ...
// }
// }
/*
Solution:
class LinkedList {
constructor() {
this.head = null;
this.tail = null;
}
append(value) {
const node = { value, next: null };
if (!this.head) {
this.head = this.tail = node;
} else {
this.tail.next = node;
this.tail = node;
}
}
[Symbol.iterator]() {
let current = this.head;
return {
next() {
if (current) {
const value = current.value;
current = current.next;
return { value, done: false };
}
return { done: true };
}
};
}
}
const list = new LinkedList();
list.append(1);
list.append(2);
list.append(3);
console.log([...list]); // [1, 2, 3]
for (const item of list) {
console.log(item); // 1, 2, 3
}
*/
// --------------------------------------------
// Exercise 15: Private Methods with Symbols
// Create a class with "private" methods using symbols
// Your code here:
// const _validate = Symbol('validate');
// const _process = Symbol('process');
//
// class DataProcessor {
// ...
// }
/*
Solution:
const _validate = Symbol('validate');
const _process = Symbol('process');
const _data = Symbol('data');
class DataProcessor {
constructor(data) {
this[_data] = data;
}
[_validate](item) {
return item !== null && item !== undefined;
}
[_process](item) {
return String(item).toUpperCase();
}
run() {
return this[_data]
.filter(item => this[_validate](item))
.map(item => this[_process](item));
}
}
const processor = new DataProcessor(['hello', null, 'world', undefined, 'test']);
console.log(processor.run()); // ['HELLO', 'WORLD', 'TEST']
// "Private" methods are hidden
console.log(Object.keys(processor)); // []
*/