Classes and prototypes

Intermediate

Introduction

When a program needs a hundred accounts, each with the same data and the same operations, writing out a hundred objects by hand is not an option. A class is a template for objects of the same shape. It keeps the data and the code that works on that data together, so that the rules for an account are written once, in one place.

How it works

new makes an object (an instance) and runs the class's constructor to set it up. The visualizer shows each instance in the heap, with its class and its own properties.

Inheritance. class Square extends Rectangle makes a subclass: a Square has everything a Rectangle has, and can add or replace methods. Its constructor must call super(...) (the parent's constructor) before using this. A method with the same name as the parent's overrides it, and super.method() calls the parent's version.

More class features.

FeatureExampleMeaning
getterget area() ...read like a property: r.area, no brackets
staticstatic count = 0belongs to the class itself: Rectangle.count
private field#balanceonly code inside the class can read it
instanceofs instanceof Rectangletrue if s was made by that class or a subclass
// A private field, a getter and a static counter.
class Account {
  static opened = 0;
  #balance = 0;
  constructor(owner) {
    this.owner = owner;
    Account.opened += 1;
  }
  deposit(amount) {
    if (amount <= 0) {
      throw new RangeError("Deposit must be positive");
    }
    this.#balance += amount;
  }
  get balance() {
    return this.#balance;
  }
}

const acc = new Account("Mitu");
acc.deposit(500);
acc.deposit(120);
console.log(acc.owner, acc.balance, Account.opened);
Open in compiler

Private fields do more than tidy things up. Since nothing outside can change #balance, the deposit method's rule can never be bypassed.

Prototypes underneath. Classes are a clearer way to write what JavaScript does with prototypes. Every object has a hidden link to another object, its prototype. When a property is not on the object itself, JavaScript looks on its prototype, then on that one's prototype, and so on: the prototype chain. Methods live once on the class's prototype, and every instance finds them through the chain, so a thousand rectangles share one area function.

Syntax

class Dog extends Animal {
  #age = 0;                 // private field
  constructor(name) {
    super(name);            // run Animal's constructor
  }
  speak() {
    return `${this.name} says woof`;
  }
}
const d = new Dog("Rex");

Example

// A class, two instances, and a subclass.
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }
  area() {
    return this.width * this.height;
  }
  describe() {
    return `${this.width} x ${this.height}, area ${this.area()}`;
  }
}

class Square extends Rectangle {
  constructor(side) {
    super(side, side);            // the Rectangle constructor
  }
  describe() {
    return "Square " + super.describe();
  }
}

const r = new Rectangle(3, 4);
const s = new Square(5);
console.log(r.describe());
console.log(s.describe());
console.log(s instanceof Rectangle, s instanceof Square);
Open in compiler

Line by line

  1. class Rectangle defines the constructor and two methods. Nothing runs yet.
  2. Square extends Rectangle, and its constructor passes side twice to super.
  3. Square replaces describe, and calls the parent's version inside its own.
  4. new Rectangle(3, 4) and new Square(5) make the two objects.
  5. s.describe() finds the Square method first. Inside it, this.area() is found on Rectangle.
  6. The last line shows that a Square is also a Rectangle.

Expected result

3 x 4, area 12
Square 5 x 5, area 25
true true

In the real world

Web browsers themselves are built this way: a button element inherits from a general HTML element, which inherits from a general node. Game characters, bank accounts and user interface widgets are common uses for classes. Plain objects and functions are still often simpler, so a class should earn its place.

Common mistakes

  • Forgetting new. Rectangle(2, 3) without it throws a TypeError for a class.
  • Using this in a subclass constructor before calling super().
  • Forgetting this. Inside a method, width alone is a variable, not the property.
  • Passing a method as a callback. setTimeout(r.show) loses its this. Use an arrow function: () => r.show().

Best practices

  • Use a class when many objects share both data and behaviour.
  • Keep data private, and offer methods that keep it valid.
  • Prefer shallow inheritance. Long chains of parents are hard to follow.

Summary

A class describes a kind of object. new makes an instance and the constructor sets it up. extends and super reuse a parent class. Getters, static members and private fields refine the design. Underneath, methods are shared through the prototype chain.

Quick check

1. What does new do?
2. In a subclass constructor, what must come before using this?
3. Where do class methods live?
4. Who can read a #private field?

Key takeaways

  • new creates an object and runs its constructor.
  • A subclass must call super before using this.
  • Private fields keep an object's data safe from outside code.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.