Skip to content
Educora
Advanced18 min13 / 27

Classes and inheritance

Create objects with the same structure using classes: constructors, methods, inheritance with `extends`, private fields, getters and static methods.

Check yourself
In this lesson you will learn
  • Write a class with a constructor and methods and create instances with new
  • Extend a class with extends and super and override a method
  • Use # private fields, getters/setters and static methods

A bank has thousands of customers, and every account has the same structure: an owner, a balance, and operations to deposit and withdraw money. Writing each account object by hand is slow and error-prone. A class is a blueprint for objects: you describe the structure once and then create as many objects from it as you like.

Class, constructor and methods

A class is declared with the keyword class, and its name starts with a capital letter. The special constructor method is called automatically whenever a new object is created, and it sets the object's properties. this refers to the specific object being created. Objects are created with the new operator; an object made from a class is called an instance.

JavaScript
class BankAccount {
  constructor(owner, balance = 0) {
    this.owner = owner;
    this.balance = balance;
  }

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

  describe() {
    return `${this.owner}: ${this.balance} AZN`;
  }
}

const aysel = new BankAccount('Aysel', 100);
const murad = new BankAccount('Murad');

aysel.deposit(50);
console.log(aysel.describe());
console.log(murad.describe());
console.log(aysel instanceof BankAccount);
▸ Expected output
Aysel: 150 AZN
Murad: 0 AZN
true
The two instances share methods but keep separate data: an operation on Aysel's account doesn't affect Murad's balance.

Inheritance: extends and super

A savings account can do everything a normal account can, and it also earns interest. To avoid repeating code, we derive the new class from the old one with extends: it inherits all the parent's methods. In the child's constructor you must call super(...) first — it runs the parent's constructor. A method with the same name overrides the parent's method, while super.describe() calls the parent's version.

JavaScript
class BankAccount {
  constructor(owner, balance = 0) {
    this.owner = owner;
    this.balance = balance;
  }
  deposit(amount) { this.balance += amount; }
  describe() { return `${this.owner}: ${this.balance} AZN`; }
}

class SavingsAccount extends BankAccount {
  constructor(owner, balance, rate) {
    super(owner, balance);
    this.rate = rate;
  }
  addInterest() { this.deposit((this.balance * this.rate) / 100); }
  describe() { return `${super.describe()} (savings, ${this.rate}%)`; }
}

const leyla = new SavingsAccount('Leyla', 1000, 5);
leyla.addInterest();
console.log(leyla.describe());
console.log(leyla instanceof BankAccount);
▸ Expected output
Leyla: 1050 AZN (savings, 5%)
true
Interactive
Loading simulation…
If Leyla called addInterest() once a year, how would her 1000 manat grow in 10 years? Change the rate and the period.

Private fields, getters and static methods

  • A field starting with # is private: it can be reached only from inside the class. Writing home.#celsius outside is a syntax error.
  • get and set methods are read and written like properties, but code runs behind them — for example, to check a value.
  • A static method belongs to the class itself, not to an instance, and is called like this: Thermostat.fromFahrenheit(68).
JavaScript
class Thermostat {
  #celsius = 20;

  get celsius() { return this.#celsius; }
  set celsius(value) {
    if (value < 5 || value > 30) {
      throw new RangeError('Temperature must be between 5 and 30');
    }
    this.#celsius = value;
  }
  get fahrenheit() { return (this.#celsius * 9) / 5 + 32; }
  static fromFahrenheit(f) {
    const t = new Thermostat();
    t.celsius = Math.round(((f - 32) * 5) / 9);
    return t;
  }
}

const home = new Thermostat();
home.celsius = 25;
console.log(home.celsius, home.fahrenheit);
try { home.celsius = 40; } catch (error) { console.log(error.name, '-', error.message); }
console.log(Thermostat.fromFahrenheit(68).celsius);
▸ Expected output
25 77
RangeError - Temperature must be between 5 and 30
20
The setter rejects a wrong value: 40 degrees is not allowed, so the temperature stays at 25.

Your own error class

A very useful application of inheritance is creating your own kinds of errors. When you extend the built-in Error class and give it a name, you can identify the kind of error precisely in a catch block with instanceof, which makes it easy to separate expected errors from unexpected ones.

JavaScript
class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = 'ValidationError';
  }
}

function register(age) {
  if (age < 13) {
    throw new ValidationError('Too young to register');
  }
  return 'Welcome!';
}

for (const age of [16, 10]) {
  try {
    console.log(register(age));
  } catch (error) {
    if (error instanceof ValidationError) {
      console.log(`${error.name}: ${error.message}`);
    } else {
      throw error;
    }
  }
}
▸ Expected output
Welcome!
ValidationError: Too young to register
TermMeaning
classa blueprint for objects
newcreates a new instance from a class
constructorsets up the new instance's properties
extends / superinheritance and calling the parent
#fielda field visible only inside the class
statica method of the class, not of an instance
Exercise

Complete the Rectangle class: the constructor stores width and height, and area() returns the area. Then write Square, which extends Rectangle: its constructor takes a single side and calls super.

Exercise · JavaScript
class Rectangle {
  constructor(width, height) {
    // save width and height
  }

  area() {
    // return the area
  }
}

class Square extends Rectangle {
  // constructor(side) → call super
}

const rect = new Rectangle(3, 4);
const square = new Square(5);
console.log(rect.area(), square.area());
console.log(square instanceof Rectangle);
console.log(square);
▸ Expected output
12 25
true
Square { width: 5, height: 5 }

Key points

  • A class is a blueprint for objects; instances are created with new.
  • The constructor sets the properties, and this points to the specific instance.
  • extends gives inheritance; a child constructor must call super() first.
  • # fields are private, get/set control a property, static belongs to the class itself.
  • You can create your own kinds of errors by extending the Error class.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
Which operator creates a new instance of a class?