Chapter 1 of ?
js 10 min read

JavaScript Mastery — Chapter 23: JS Classes & Object-Oriented Programming

Module 5: Advanced Functions & OOP Chapter 23 30 min read

JS Classes & Object-Oriented Programming

Master modern ECMAScript Class architecture: constructors, instance methods, static members, inheritance with extends and super(), true private fields and methods (#private), getters/setters, static initialization blocks, and polymorphism.

Class Hierarchy & Memory Structure

Under the hood, ES6 class syntax is elegant syntactic sugar over prototypes. Subclasses establish dual prototype links: one between instance prototypes for shared methods, and another between constructor functions for static inheritance.

class Vehicle (Base / Super) static totalVehicles = 0; constructor(make, model) { ... } startEngine() { ... } #vinNumber; // True Private Defines common mobility contract extends class ElectricCar extends Vehicle #batteryCapacityKWh = 75; constructor(make, model, kwh) { super(make, model); // Required! charge() { ... } // Subclass method startEngine() { ... } // Polymorphic Inherits both instance & static members

1. Anatomy of Modern JavaScript Classes

The class keyword introduces a clean, unified syntax for defining object constructors, instance fields, methods, and getters/setters:

Complete Modern Class Declaration

class BankAccount {
  // 1. Public Class Field (ES2022)
  accountType = 'SAVINGS';

  // 2. Private Field (Cannot be accessed outside this class!)
  #balance = 0;
  #accountNumber;

  // 3. Static Property (Belongs to class, not instances)
  static minimumDeposit = 50;

  // 4. Constructor (Instantiates new objects)
  constructor(accountNumber, initialDeposit = 0) {
    if (initialDeposit < BankAccount.minimumDeposit) {
      throw new Error(`Minimum deposit is $${BankAccount.minimumDeposit}`);
    }
    this.#accountNumber = accountNumber;
    this.#balance = initialDeposit;
  }

  // 5. Getter & Setter (Virtual properties)
  get balance() {
    return this.#balance;
  }

  // 6. Instance Method
  deposit(amount) {
    if (amount <= 0) throw new Error('Invalid deposit');
    this.#balance += amount;
    return this.#balance;
  }
}

const myAcct = new BankAccount('ACC-101', 200);
myAcct.deposit(100);
console.log('Balance:', myAcct.balance); // 300
// myAcct.#balance -> SyntaxError: Private field '#balance' must be declared in an enclosing class
Try it in Playground

2. Inheritance with extends & super()

JavaScript classes support single inheritance using extends. In a derived subclass, the super() constructor call is mandatory before accessing this:

Subclassing & Polymorphism

class Employee {
  constructor(name, baseSalary) {
    this.name = name;
    this.baseSalary = baseSalary;
  }

  calculatePay() {
    return this.baseSalary;
  }
}

class Manager extends Employee {
  #bonus = 0;

  constructor(name, baseSalary, bonus) {
    // CRITICAL: Must invoke super() before touching 'this'!
    super(name, baseSalary);
    this.#bonus = bonus;
  }

  // Polymorphic method override:
  calculatePay() {
    // Calling super.method() invokes parent implementation:
    return super.calculatePay() + this.#bonus;
  }
}

const emp = new Employee('Bob', 5000);
const mgr = new Manager('Alice', 8000, 3000);

console.log(emp.calculatePay()); // 5000
console.log(mgr.calculatePay()); // 11000 (8000 + 3000)
console.log(mgr instanceof Employee); // true (Inherits prototype!)
Try it in Playground
The super() Order Rule
In a derived constructor, attempting to read or write this.property before calling super() throws a ReferenceError: Must call super constructor in derived class before accessing 'this'.

3. Static Members & Static Initialization Blocks (ES2022)

Static methods and fields are attached directly to the constructor function rather than to instances. ES2022 added Static Initialization Blocks to permit complex static bootstrapping with private access:

Static Factory & Static Blocks

class DatabaseConnection {
  static #pool = [];
  static maxConnections;

  // ES2022 Static Initialization Block:
  static {
    // Runs once when class is evaluated:
    try {
      this.maxConnections = 10;
      console.log('Database pool initialized with limit:', this.maxConnections);
    } catch {
      this.maxConnections = 1;
    }
  }

  // Static Factory Method:
  static create(connectionString) {
    if (this.#pool.length >= this.maxConnections) {
      throw new Error('Connection pool exhausted!');
    }
    const conn = new DatabaseConnection(connectionString);
    this.#pool.push(conn);
    return conn;
  }

  constructor(connString) {
    this.connString = connString;
  }
}

const db = DatabaseConnection.create('mongodb://localhost:27017');
Try it in Playground

Interactive Mini-Lab: OOP Hierarchy Simulator

Instantiate classes dynamically, test polymorphic method dispatch, and verify true private field enforcement:

// Click actions above to test class inheritance & private fields...

Hands-on Challenge: Production EventEmitter Class

Coding Challenge

Build a production-grade EventEmitter class using modern ES6 syntax and true private state. The class must manage an internal subscriber dictionary without exposing internal arrays to outside manipulation:

EventEmitter Requirements
  1. Store events in a private #events = new Map(); field.
  2. on(eventName, listener): registers an event listener callback.
  3. emit(eventName, ...args): invokes all registered listeners synchronously with passed arguments.
  4. off(eventName, listener): removes a specific registered listener.
  5. once(eventName, listener): registers a listener that automatically removes itself after its first execution!
Production EventEmitter Class Implementation
/**
 * Professional EventEmitter class utilizing true private fields (#).
 */
class EventEmitter {
  // Private listener dictionary:
  #events = new Map();

  on(event, listener) {
    if (typeof listener !== 'function') throw new TypeError('Listener must be a function');
    if (!this.#events.has(event)) {
      this.#events.set(event, []);
    }
    this.#events.get(event).push(listener);
    return this; // Enable method chaining
  }

  emit(event, ...args) {
    const listeners = this.#events.get(event);
    if (!listeners || listeners.length === 0) return false;

    // Iterate over a copy so listeners modifying the array during emit don't cause skip bugs:
    [...listeners].forEach(listener => listener(...args));
    return true;
  }

  off(event, listener) {
    const listeners = this.#events.get(event);
    if (!listeners) return this;

    const filtered = listeners.filter(fn => fn !== listener);
    if (filtered.length === 0) {
      this.#events.delete(event);
    } else {
      this.#events.set(event, filtered);
    }
    return this;
  }

  once(event, listener) {
    const wrapper = (...args) => {
      this.off(event, wrapper);
      listener(...args);
    };
    return this.on(event, wrapper);
  }
}

// Verification:
const emitter = new EventEmitter();
const onUserLogin = user => console.log('Welcome:', user.name);

emitter.on('login', onUserLogin);
emitter.emit('login', { name: 'Sarah' }); // Welcome: Sarah
emitter.off('login', onUserLogin);
emitter.emit('login', { name: 'Sarah' }); // Silent (unsubscribed)

Chapter 23 Knowledge Check

Validate your mastery of ES6 classes, inheritance, and object-oriented principles.

1. In a derived subclass constructor, what occurs if you attempt to access this.name = 'Bob' BEFORE calling super()?
JavaScript quietly creates a global variable named name.
A ReferenceError is thrown immediately.
The parent constructor is automatically called with no arguments.
The assignment succeeds with zero warnings.
2. What prefix is used in modern ECMAScript to define hard-private class fields that cannot be accessed outside the class body?
An underscore (_password)
A hash symbol (#password)
The keyword private password
A dollar sign ($password)
3. How are static methods defined on a class invoked?
Through an instance: const u = new User(); u.staticMethod()
Directly through the class constructor: User.staticMethod()
Only through a super() call in subclasses.
Using the call() or apply() methods exclusively.
4. Under the hood, what is a JavaScript class?
Syntactic sugar over prototype-based inheritance and constructor functions.
A completely new compiled binary structure similar to C++.
A WebAssembly module.
A plain JSON dictionary.
5. When does an ES2022 Static Initialization Block (static { ... }) execute?
Every time a new instance is created with new.
Exactly once, at the time the class definition is initially evaluated.
Only when an uncaught exception is thrown.
After the DOMContentLoaded event fires.
Done with this chapter?
Mark it complete to track your progress and unlock your certificate.
Next Up
—

Learner Reviews

Write a Review
Share your experience to help other learners.
Your Rating *
★ ★ ★ ★ ★