Advanced30 min read

Classes & Inheritance

Learn ES6 class syntax for creating objects, using constructors, methods, getters/setters, static members, and inheritance with extends.

Class Syntax

ES6 introduced the class keyword as a cleaner syntax for creating objects with shared behavior. A class is essentially a blueprint for creating objects.

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

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

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

Key parts:

  • constructor — a special method called automatically when you create an instance with new. It initializes the object's properties.
  • Instance methods — functions defined inside the class that are available on every instance (e.g., greet()).
  • new keyword — creates a new instance of the class.

Classes are syntactic sugar over JavaScript's existing prototype-based inheritance. Under the hood, class Person still creates a constructor function and adds methods to Person.prototype. But the class syntax is much more readable.

Unlike function declarations, class declarations are NOT hoisted:

javascript
const p = new Person('Alice', 30); // ReferenceError!
class Person { ... }

You must define the class before using it. This is by design — it prevents subtle bugs caused by hoisting.

Getters & Setters

Getters and setters let you define computed properties and add validation when setting values. They look like methods but are accessed like properties (no parentheses).

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

  // Getter — computed property
  get area() {
    return Math.PI * this.radius ** 2;
  }

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

  // Setter — validation on assignment
  set radius(value) {
    if (value < 0) {
      throw new RangeError('Radius cannot be negative');
    }
    this._radius = value;
  }

  get radius() {
    return this._radius;
  }
}

const c = new Circle(5);
console.log(c.area);     // 78.539... (no parentheses!)
console.log(c.diameter); // 10
c.radius = 10;           // uses the setter
// c.radius = -1;        // throws RangeError

ES2022 introduced private fields with the # prefix. Private fields are truly inaccessible from outside the class:

javascript
class BankAccount {
  #balance = 0; // private field

  deposit(amount) {
    this.#balance += amount;
  }

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

const account = new BankAccount();
account.deposit(100);
console.log(account.balance);  // 100
// console.log(account.#balance); // SyntaxError!

Static Methods & Properties

Static methods and properties belong to the class itself, not to instances. They are called on the class, not on objects created from it.

javascript
class MathUtils {
  static PI = 3.14159;

  static square(n) {
    return n * n;
  }

  static cube(n) {
    return n * n * n;
  }
}

console.log(MathUtils.PI);        // 3.14159
console.log(MathUtils.square(4)); // 16
console.log(MathUtils.cube(3));   // 27

// Cannot call on instances:
const m = new MathUtils();
// m.square(4); // TypeError: m.square is not a function

Static methods are commonly used for:

  • Utility functions that do not need instance data
  • Factory methods that create instances in specific ways:
    javascript
    class User {
      constructor(name, role) {
        this.name = name;
        this.role = role;
      }
    
      static createAdmin(name) {
        return new User(name, 'admin');
      }
    
      static createGuest() {
        return new User('Guest', 'guest');
      }
    }
    
    const admin = User.createAdmin('Alice');
    const guest = User.createGuest();

You have already used built-in static methods: Array.isArray(), Object.keys(), Date.now(), JSON.parse(). These all belong to the class, not to instances.

Inheritance with extends

The extends keyword creates a child class that inherits all properties and methods from a parent class:

javascript
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'); // call parent constructor
    this.tricks = [];
  }

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

  showTricks() {
    return `${this.name} knows: ${this.tricks.join(', ')}`;
  }
}

const rex = new Dog('Rex');
console.log(rex.speak());      // 'Rex says Woof!' (inherited)
rex.learn('sit');
rex.learn('shake');
console.log(rex.showTricks()); // 'Rex knows: sit, shake'

Key rules for inheritance:

  1. super() must be called in the child constructor before using this. It calls the parent's constructor.
  2. Child classes can override parent methods by defining a method with the same name.
  3. Use super.method() to call the parent's version of an overridden method:
    javascript
    class Cat extends Animal {
      speak() {
        return super.speak() + ' (purrs)';
      }
    }
  4. The instanceof operator checks the inheritance chain:
    javascript
    console.log(rex instanceof Dog);    // true
    console.log(rex instanceof Animal); // true

Inheritance creates an "is-a" relationship. A Dog is an Animal. Use it when there is a clear hierarchy, but prefer composition (objects containing other objects) when the relationship is "has-a".

Classes in Action

html
<div id="output"></div>

<script>
  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');
      this.tricks = [];
    }

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

    describe() {
      const base = this.speak();
      const tricks = this.tricks.length
        ? ` Knows: ${this.tricks.join(', ')}`
        : ' No tricks yet.';
      return base + tricks;
    }
  }

  const dog = new Dog('Buddy');
  dog.learn('sit');
  dog.learn('roll over');

  const cat = new Animal('Whiskers', 'Meow');

  const output = document.getElementById('output');
  output.innerHTML = `
    <p>${dog.describe()}</p>
    <p>${cat.speak()}</p>
    <p>Is Dog an Animal? ${dog instanceof Animal}</p>
  `;
</script>

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

Ready to practice?

Create your free account to access the interactive code editor, run challenges, and track your progress.