javascript

examples

examples.js
/**
 * ========================================
 * 8.1 CLASS BASICS - CODE EXAMPLES
 * ========================================
 */

/**
 * EXAMPLE 1: Basic Class Declaration
 * Simple class with constructor and methods
 */
console.log('--- Example 1: Basic Class Declaration ---');

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    return `Hello, I'm ${this.name}`;
  }

  introduce() {
    return `I'm ${this.name}, ${this.age} years old`;
  }
}

const alice = new Person('Alice', 30);
console.log(alice.greet()); // "Hello, I'm Alice"
console.log(alice.introduce()); // "I'm Alice, 30 years old"

/**
 * EXAMPLE 2: Class Type and Nature
 * Classes are functions under the hood
 */
console.log('\n--- Example 2: Class Type ---');

class Example {}

console.log(typeof Example); // "function"
console.log(Example.prototype.constructor === Example); // true
console.log(Example === Example.prototype.constructor); // true

// Class is NOT hoisted like function declarations
// console.log(new NotYetDefined()); // ReferenceError
// class NotYetDefined {}

/**
 * EXAMPLE 3: Constructor with Default Parameters
 * Using defaults and options object
 */
console.log('\n--- Example 3: Constructor Defaults ---');

class Config {
  constructor({
    host = 'localhost',
    port = 3000,
    debug = false,
    timeout = 5000,
  } = {}) {
    this.host = host;
    this.port = port;
    this.debug = debug;
    this.timeout = timeout;
  }

  getUrl() {
    return `http://${this.host}:${this.port}`;
  }
}

const defaultConfig = new Config();
console.log(defaultConfig.getUrl()); // "http://localhost:3000"

const customConfig = new Config({ port: 8080, debug: true });
console.log(customConfig.port); // 8080
console.log(customConfig.debug); // true

/**
 * EXAMPLE 4: Instance Methods
 * Methods are shared via prototype
 */
console.log('\n--- Example 4: Instance Methods ---');

class Counter {
  constructor(initial = 0) {
    this.count = initial;
  }

  increment() {
    this.count++;
    return this;
  }

  decrement() {
    this.count--;
    return this;
  }

  reset() {
    this.count = 0;
    return this;
  }

  getCount() {
    return this.count;
  }
}

const counter1 = new Counter(5);
const counter2 = new Counter(10);

// Methods are shared
console.log(counter1.increment === counter2.increment); // true

// Chaining works
counter1.increment().increment().increment();
console.log(counter1.getCount()); // 8

/**
 * EXAMPLE 5: Getter and Setter
 * Controlled property access
 */
console.log('\n--- Example 5: Getters and Setters ---');

class Temperature {
  constructor(celsius = 0) {
    this._celsius = celsius;
  }

  // Getter
  get celsius() {
    return this._celsius;
  }

  // Setter with validation
  set celsius(value) {
    if (value < -273.15) {
      throw new RangeError('Temperature below absolute zero');
    }
    this._celsius = value;
  }

  // Computed getters
  get fahrenheit() {
    return (this._celsius * 9) / 5 + 32;
  }

  set fahrenheit(value) {
    this._celsius = ((value - 32) * 5) / 9;
  }

  get kelvin() {
    return this._celsius + 273.15;
  }

  set kelvin(value) {
    this._celsius = value - 273.15;
  }
}

const temp = new Temperature(25);
console.log(`${temp.celsius}°C = ${temp.fahrenheit}°F = ${temp.kelvin}K`);

temp.fahrenheit = 100;
console.log(`${temp.celsius.toFixed(2)}°C`); // "37.78°C"

/**
 * EXAMPLE 6: Read-Only Properties (Getter Only)
 * Properties that can't be set
 */
console.log('\n--- Example 6: Read-Only Properties ---');

class Circle {
  constructor(radius) {
    this._radius = radius;
  }

  get radius() {
    return this._radius;
  }

  // No setter - read-only

  get diameter() {
    return this._radius * 2;
  }

  get circumference() {
    return 2 * Math.PI * this._radius;
  }

  get area() {
    return Math.PI * this._radius ** 2;
  }
}

const circle = new Circle(5);
console.log(`Radius: ${circle.radius}`);
console.log(`Diameter: ${circle.diameter}`);
console.log(`Circumference: ${circle.circumference.toFixed(2)}`);
console.log(`Area: ${circle.area.toFixed(2)}`);

// Attempting to set read-only property
// circle.radius = 10; // Fails silently (or throws in strict mode)

/**
 * EXAMPLE 7: Computed Method Names
 * Dynamic method names using expressions
 */
console.log('\n--- Example 7: Computed Method Names ---');

const PREFIX = 'get';
const SUFFIX = 'Value';

class DynamicMethods {
  constructor() {
    this.data = { x: 10, y: 20 };
  }

  // Computed method name
  [`${PREFIX}${SUFFIX}`]() {
    return this.data;
  }

  // Using variable
  [Symbol.toStringTag] = 'DynamicMethods';

  // Symbol.iterator
  *[Symbol.iterator]() {
    for (const key in this.data) {
      yield [key, this.data[key]];
    }
  }
}

const dynamic = new DynamicMethods();
console.log(dynamic.getValue()); // { x: 10, y: 20 }
console.log([...dynamic]); // [["x", 10], ["y", 20]]
console.log(Object.prototype.toString.call(dynamic)); // "[object DynamicMethods]"

/**
 * EXAMPLE 8: Class Expressions
 * Anonymous and named class expressions
 */
console.log('\n--- Example 8: Class Expressions ---');

// Anonymous class expression
const Animal = class {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a sound`);
  }
};

// Named class expression
const Dog = class DogClass {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} barks`);
  }

  // Can use DogClass inside the class
  clone() {
    return new DogClass(this.name);
  }
};

const animal = new Animal('Generic');
animal.speak(); // "Generic makes a sound"

const dog = new Dog('Rex');
dog.speak(); // "Rex barks"

const dogClone = dog.clone();
console.log(dogClone.name); // "Rex"

/**
 * EXAMPLE 9: Class as First-Class Citizen
 * Passing classes as arguments
 */
console.log('\n--- Example 9: Class as First-Class Citizen ---');

function createInstance(Class, ...args) {
  return new Class(...args);
}

function registerClass(Class, registry) {
  registry[Class.name] = Class;
  return Class;
}

class User {
  constructor(name) {
    this.name = name;
  }
}

class Product {
  constructor(name, price) {
    this.name = name;
    this.price = price;
  }
}

const user = createInstance(User, 'Alice');
const product = createInstance(Product, 'Widget', 29.99);

console.log(user); // User { name: "Alice" }
console.log(product); // Product { name: "Widget", price: 29.99 }

const registry = {};
registerClass(User, registry);
registerClass(Product, registry);
console.log(Object.keys(registry)); // ["User", "Product"]

/**
 * EXAMPLE 10: Immediately Invoked Class Expression
 * Creating singleton-like objects
 */
console.log('\n--- Example 10: IICE (Singleton) ---');

const AppState = new (class {
  constructor() {
    this.state = {};
    this.listeners = [];
  }

  get(key) {
    return this.state[key];
  }

  set(key, value) {
    this.state[key] = value;
    this.notify(key, value);
  }

  subscribe(callback) {
    this.listeners.push(callback);
    return () => {
      const index = this.listeners.indexOf(callback);
      if (index > -1) this.listeners.splice(index, 1);
    };
  }

  notify(key, value) {
    for (const listener of this.listeners) {
      listener(key, value);
    }
  }
})();

AppState.subscribe((key, value) => {
  console.log(`State changed: ${key} = ${value}`);
});

AppState.set('user', 'Alice'); // "State changed: user = Alice"
console.log(AppState.get('user')); // "Alice"

/**
 * EXAMPLE 11: Method Chaining Pattern
 * Fluent interface design
 */
console.log('\n--- Example 11: Method Chaining ---');

class StringBuilder {
  constructor(initial = '') {
    this.value = initial;
  }

  append(str) {
    this.value += str;
    return this;
  }

  prepend(str) {
    this.value = str + this.value;
    return this;
  }

  wrap(prefix, suffix) {
    this.value = prefix + this.value + suffix;
    return this;
  }

  uppercase() {
    this.value = this.value.toUpperCase();
    return this;
  }

  lowercase() {
    this.value = this.value.toLowerCase();
    return this;
  }

  trim() {
    this.value = this.value.trim();
    return this;
  }

  toString() {
    return this.value;
  }
}

const result = new StringBuilder('hello')
  .append(' world')
  .uppercase()
  .wrap('[', ']')
  .toString();

console.log(result); // "[HELLO WORLD]"

/**
 * EXAMPLE 12: Validation in Constructor
 * Ensuring valid object creation
 */
console.log('\n--- Example 12: Constructor Validation ---');

class Email {
  constructor(address) {
    if (!address || typeof address !== 'string') {
      throw new TypeError('Email address must be a string');
    }

    if (!Email.isValid(address)) {
      throw new Error(`Invalid email format: ${address}`);
    }

    this.address = address.toLowerCase();
    this.domain = this.address.split('@')[1];
  }

  static isValid(address) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(address);
  }

  toString() {
    return this.address;
  }
}

try {
  const email = new Email('Alice@Example.COM');
  console.log(email.address); // "alice@example.com"
  console.log(email.domain); // "example.com"

  // const invalid = new Email("not-an-email"); // Throws Error
} catch (e) {
  console.error(e.message);
}

/**
 * EXAMPLE 13: this Binding in Methods
 * Understanding method context
 */
console.log('\n--- Example 13: this Binding ---');

class Button {
  constructor(label) {
    this.label = label;
    this.clickCount = 0;

    // Option 1: Bind in constructor
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.clickCount++;
    console.log(`${this.label} clicked ${this.clickCount} times`);
  }

  // Option 2: Arrow function property (requires class fields)
  // handleClick = () => {
  //     this.clickCount++;
  //     console.log(`${this.label} clicked ${this.clickCount} times`);
  // };
}

const button = new Button('Submit');

// Direct call - works
button.handleClick(); // "Submit clicked 1 times"

// As callback - works because of bind
const callback = button.handleClick;
callback(); // "Submit clicked 2 times" (would fail without bind)

/**
 * EXAMPLE 14: Generator Methods
 * Iterators in classes
 */
console.log('\n--- Example 14: Generator Methods ---');

class Range {
  constructor(start, end, step = 1) {
    this.start = start;
    this.end = end;
    this.step = step;
  }

  *[Symbol.iterator]() {
    for (let i = this.start; i <= this.end; i += this.step) {
      yield i;
    }
  }

  *reversed() {
    for (let i = this.end; i >= this.start; i -= this.step) {
      yield i;
    }
  }
}

const range = new Range(1, 5);
console.log([...range]); // [1, 2, 3, 4, 5]
console.log([...range.reversed()]); // [5, 4, 3, 2, 1]

const evenRange = new Range(0, 10, 2);
console.log([...evenRange]); // [0, 2, 4, 6, 8, 10]

/**
 * EXAMPLE 15: Async Methods
 * Asynchronous operations in classes
 */
console.log('\n--- Example 15: Async Methods ---');

class DataFetcher {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
    this.cache = new Map();
  }

  async fetch(endpoint) {
    const url = `${this.baseUrl}${endpoint}`;

    if (this.cache.has(url)) {
      console.log('Returning cached data');
      return this.cache.get(url);
    }

    console.log(`Fetching: ${url}`);
    // Simulate API call
    const data = await this.simulateFetch(endpoint);
    this.cache.set(url, data);
    return data;
  }

  // Simulate fetch for demo
  async simulateFetch(endpoint) {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve({ endpoint, timestamp: Date.now() });
      }, 100);
    });
  }

  clearCache() {
    this.cache.clear();
  }
}

(async () => {
  const fetcher = new DataFetcher('https://api.example.com');
  const data1 = await fetcher.fetch('/users');
  console.log(data1);

  const data2 = await fetcher.fetch('/users'); // From cache
  console.log(data1 === data2); // true
})();

/**
 * EXAMPLE 16: Class with Symbol Properties
 * Using symbols for special behavior
 */
console.log('\n--- Example 16: Symbol Properties ---');

class Collection {
  constructor(items = []) {
    this.items = [...items];
  }

  // Make it iterable
  *[Symbol.iterator]() {
    yield* this.items;
  }

  // Custom string tag
  get [Symbol.toStringTag]() {
    return 'Collection';
  }

  // Custom primitive conversion
  [Symbol.toPrimitive](hint) {
    if (hint === 'number') {
      return this.items.length;
    }
    if (hint === 'string') {
      return `Collection(${this.items.join(', ')})`;
    }
    return this.items;
  }

  add(item) {
    this.items.push(item);
    return this;
  }

  get length() {
    return this.items.length;
  }
}

const collection = new Collection([1, 2, 3]);
console.log([...collection]); // [1, 2, 3]
console.log(String(collection)); // "Collection(1, 2, 3)"
console.log(+collection); // 3
console.log(Object.prototype.toString.call(collection)); // "[object Collection]"

/**
 * EXAMPLE 17: Multiple Constructors Pattern
 * Factory methods for different creation patterns
 */
console.log('\n--- Example 17: Multiple Constructors ---');

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  // Alternative constructor from polar coordinates
  static fromPolar(r, theta) {
    return new Point(r * Math.cos(theta), r * Math.sin(theta));
  }

  // From array
  static fromArray([x, y]) {
    return new Point(x, y);
  }

  // From object
  static fromObject({ x, y }) {
    return new Point(x, y);
  }

  // Clone
  static from(point) {
    return new Point(point.x, point.y);
  }

  distanceTo(other) {
    return Math.sqrt((this.x - other.x) ** 2 + (this.y - other.y) ** 2);
  }

  toString() {
    return `(${this.x}, ${this.y})`;
  }
}

const p1 = new Point(3, 4);
const p2 = Point.fromPolar(5, Math.PI / 4);
const p3 = Point.fromArray([1, 2]);
const p4 = Point.fromObject({ x: 5, y: 6 });

console.log(p1.toString()); // "(3, 4)"
console.log(p2.toString()); // "(3.54, 3.54)" approximately
console.log(p1.distanceTo(p4)); // ~2.83

/**
 * EXAMPLE 18: Class Comparison to Constructor Function
 * Side-by-side comparison
 */
console.log('\n--- Example 18: Class vs Constructor ---');

// ES6 Class
class PersonClass {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `Hello from ${this.name}`;
  }
}

// Equivalent Constructor Function
function PersonFunction(name) {
  this.name = name;
}

PersonFunction.prototype.greet = function () {
  return `Hello from ${this.name}`;
};

const classInstance = new PersonClass('Alice');
const funcInstance = new PersonFunction('Bob');

console.log(classInstance.greet()); // "Hello from Alice"
console.log(funcInstance.greet()); // "Hello from Bob"

// Key differences
console.log('\nDifferences:');

// 1. Methods enumerable
for (const key in classInstance) {
  console.log(`Class enumerable: ${key}`); // Only "name"
}
for (const key in funcInstance) {
  console.log(`Function enumerable: ${key}`); // "name" AND "greet"
}

// 2. Calling without new
try {
  PersonClass('Test'); // Throws TypeError
} catch (e) {
  console.log('Class without new: TypeError');
}

const result2 = PersonFunction('Test'); // Returns undefined, creates global
console.log("Function without new: Works (but shouldn't)");

/**
 * EXAMPLE 19: Complex Class Example
 * Full-featured class demonstration
 */
console.log('\n--- Example 19: Complex Class Example ---');

class BankAccount {
  constructor(accountNumber, holder, initialBalance = 0) {
    this._accountNumber = accountNumber;
    this._holder = holder;
    this._balance = initialBalance;
    this._transactions = [];

    this._log('Account created');
  }

  // Getters
  get accountNumber() {
    return this._accountNumber;
  }

  get holder() {
    return this._holder;
  }

  get balance() {
    return this._balance;
  }

  get transactions() {
    return [...this._transactions]; // Return copy
  }

  // Methods
  deposit(amount) {
    if (amount <= 0) {
      throw new Error('Deposit amount must be positive');
    }

    this._balance += amount;
    this._log(`Deposit: +${amount}`);
    return this;
  }

  withdraw(amount) {
    if (amount <= 0) {
      throw new Error('Withdrawal amount must be positive');
    }

    if (amount > this._balance) {
      throw new Error('Insufficient funds');
    }

    this._balance -= amount;
    this._log(`Withdrawal: -${amount}`);
    return this;
  }

  transfer(toAccount, amount) {
    this.withdraw(amount);
    toAccount.deposit(amount);
    this._log(`Transfer to ${toAccount.accountNumber}: -${amount}`);
    return this;
  }

  // Private helper
  _log(message) {
    this._transactions.push({
      message,
      timestamp: new Date(),
      balance: this._balance,
    });
  }

  // String representation
  toString() {
    return `Account ${this._accountNumber} (${this._holder}): $${this._balance}`;
  }
}

const checking = new BankAccount('CHK001', 'Alice', 1000);
const savings = new BankAccount('SAV001', 'Alice', 5000);

checking.deposit(500).withdraw(200);
savings.transfer(checking, 1000);

console.log(checking.toString()); // Account CHK001 (Alice): $2300
console.log(`Transactions: ${checking.transactions.length}`);

/**
 * EXAMPLE 20: Class with Validation and Events
 * Combining patterns
 */
console.log('\n--- Example 20: Advanced Class Pattern ---');

class Form {
  constructor(fields = {}) {
    this._fields = {};
    this._errors = {};
    this._listeners = new Map();

    for (const [name, config] of Object.entries(fields)) {
      this.addField(name, config);
    }
  }

  addField(name, { value = '', validators = [] } = {}) {
    this._fields[name] = { value, validators };
    return this;
  }

  get(name) {
    return this._fields[name]?.value;
  }

  set(name, value) {
    if (!this._fields[name]) {
      throw new Error(`Unknown field: ${name}`);
    }

    const oldValue = this._fields[name].value;
    this._fields[name].value = value;

    this._emit('change', { field: name, oldValue, newValue: value });
    return this;
  }

  validate() {
    this._errors = {};
    let isValid = true;

    for (const [name, field] of Object.entries(this._fields)) {
      const fieldErrors = [];

      for (const validator of field.validators) {
        const error = validator(field.value);
        if (error) fieldErrors.push(error);
      }

      if (fieldErrors.length > 0) {
        this._errors[name] = fieldErrors;
        isValid = false;
      }
    }

    this._emit('validate', { valid: isValid, errors: this._errors });
    return isValid;
  }

  get errors() {
    return { ...this._errors };
  }

  get data() {
    return Object.fromEntries(
      Object.entries(this._fields).map(([k, v]) => [k, v.value])
    );
  }

  on(event, callback) {
    if (!this._listeners.has(event)) {
      this._listeners.set(event, []);
    }
    this._listeners.get(event).push(callback);
    return this;
  }

  _emit(event, data) {
    if (this._listeners.has(event)) {
      for (const callback of this._listeners.get(event)) {
        callback(data);
      }
    }
  }
}

// Validators
const required = (value) => (!value ? 'This field is required' : null);
const email = (value) =>
  value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
    ? 'Invalid email format'
    : null;
const minLength = (min) => (value) =>
  value && value.length < min ? `Minimum ${min} characters` : null;

// Usage
const form = new Form({
  name: { value: '', validators: [required, minLength(2)] },
  email: { value: '', validators: [required, email] },
});

form.on('change', ({ field, newValue }) => {
  console.log(`Field "${field}" changed to "${newValue}"`);
});

form.on('validate', ({ valid, errors }) => {
  console.log(`Form valid: ${valid}`);
  if (!valid) console.log('Errors:', errors);
});

form.set('name', 'A');
form.set('email', 'invalid-email');

form.validate();
// Form valid: false
// Errors: { name: ["Minimum 2 characters"], email: ["Invalid email format"] }

form.set('name', 'Alice');
form.set('email', 'alice@example.com');
form.validate();
// Form valid: true

console.log('Form data:', form.data);

console.log('\n========================================');
console.log('End of Class Basics Examples');
console.log('========================================');
Examples - JavaScript Tutorial | DeepML