⚡IntermediateObject-Oriented JavaScript

Classes

Define blueprints for objects using ES6 classes, constructors, methods, inheritance with extends, and static members.

Overview

ES6 classes provide a clean, readable syntax for defining object blueprints in JavaScript. Before classes, you used constructor functions and prototype manipulation directly classes are syntactic sugar over that same prototype-based system, but far more ergonomic. A class defines a constructor (the function that runs when you use new), instance methods (available on every object created from the class), static methods (called on the class itself, not instances), and private fields (prefixed with # to be truly private). Classes support inheritance via extends, and the super keyword calls the parent class's constructor or methods. Classes are especially useful for modeling entities in your domain: User, Product, EventEmitter, LinkedList. They group related data and behavior together, making code more organized and maintainable. While JavaScript is not a purely object-oriented language, understanding classes is essential for working with modern frameworks like React (class components), TypeScript, and many libraries.

Key Points

  • •class ClassName { } defines a class; new ClassName() creates an instance
  • •constructor() runs when creating an instance use it to set initial properties
  • •Instance methods are defined inside the class body without the function keyword
  • •static methods belong to the class itself, not instances call them as ClassName.method()
  • •extends creates a subclass; the subclass must call super() before using this in the constructor
  • •Private fields use # prefix (e.g. #password) inaccessible from outside the class
  • •Getters (get prop()) and setters (set prop(val)) provide computed/validated property access
  • •Classes are NOT hoisted like function declarations you must define them before using them

Examples

Defining a class with constructor, methods, and a static method

class BankAccount {
  #balance;  // private field

  constructor(owner, initialBalance = 0) {
    this.owner = owner;
    this.#balance = initialBalance;
  }

  deposit(amount) {
    if (amount <= 0) throw new RangeError("Amount must be positive");
    this.#balance += amount;
    return this;  // enable chaining
  }

  withdraw(amount) {
    if (amount > this.#balance) throw new Error("Insufficient funds");
    this.#balance -= amount;
    return this;
  }

  get balance() {
    return this.#balance;
  }

  toString() {
    return `${this.owner}: $${this.#balance}`;
  }

  static create(owner) {
    return new BankAccount(owner);
  }
}

const acc = BankAccount.create("Alice");
acc.deposit(100).deposit(50).withdraw(30);
console.log(acc.balance);   // 120
console.log(acc.toString()); // Alice: $120

Inheritance with extends and super

class Animal {
  constructor(name, sound) {
    this.name = name;
    this.sound = sound;
  }

  speak() {
    return `${this.name} says ${this.sound}`;
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name, "woof");  // must call super() before using this
    this.tricks = [];
  }

  learn(trick) {
    this.tricks.push(trick);
    return this;
  }

  perform() {
    if (this.tricks.length === 0) return `${this.name} doesn't know any tricks`;
    return `${this.name} performs: ${this.tricks.join(", ")}`;
  }
}

const dog = new Dog("Rex");
dog.learn("sit").learn("shake");
console.log(dog.speak());    // Rex says woof
console.log(dog.perform());  // Rex performs: sit, shake
console.log(dog instanceof Animal); // true

Getters, setters, and input validation

class Temperature {
  #celsius;

  constructor(celsius) {
    this.celsius = celsius;  // uses the setter
  }

  get celsius() {
    return this.#celsius;
  }

  set celsius(value) {
    if (typeof value !== "number") throw new TypeError("Temperature must be a number");
    if (value < -273.15) throw new RangeError("Below absolute zero");
    this.#celsius = value;
  }

  get fahrenheit() {
    return this.#celsius * 9 / 5 + 32;
  }

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

const t = new Temperature(100);
console.log(t.fahrenheit);  // 212
t.fahrenheit = 32;
console.log(t.celsius);     // 0

Common Mistakes

  • Forgetting to call super() in a subclass constructor before accessing this throws a ReferenceError
  • Defining methods with const/let inside the class body method definitions have no keyword
  • Using a class before it is defined unlike function declarations, classes are not hoisted
  • Expecting private fields (#name) to work in very old browsers they are ES2022 and need transpilation for legacy targets

Note

Favor composition over inheritance when possible: rather than deep class hierarchies, give objects capabilities by composing smaller, focused classes or plain objects. Deep inheritance chains become hard to follow and change.

Exercises

Write JavaScript code to solve each exercise. Run it and check your output.

Loading editor…

Completed: 0 / 5 exercises

Knowledge Check

Question 1 of 4

What must you call before using `this` in a subclass constructor?