Appearance
Class Inheritance
The extends keyword enables classes to inherit functionality from other classes. This lets you create hierarchies of related classes, override parent methods, and reuse code. While built on JavaScript's prototype system, class inheritance provides a more convenient syntax for these relationships.
Basic Inheritance
We can extend a base class to create more specific classes:
js
class Shape {
constructor(colour) {
this.colour = colour;
}
draw() {
return `A ${this.colour} shape.`;
}
}
class Square extends Shape {
constructor(colour, size) {
super(colour); // Call parent constructor
this.size = size;
}
draw() {
return `A ${this.colour} square with size ${this.size}`;
}
}
class Circle extends Shape {
constructor(colour, radius) {
super(colour); // Call parent constructor
this.radius = radius;
}
draw() {
return `A ${this.colour} circle with radius ${this.radius}`;
}
}
// Create different types of shapes
const square = new Square("red", 10);
const circle = new Circle("blue", 5);
console.log(square.draw()); // "A red square with size 10"
console.log(circle.draw()); // "A blue circle with radius 5"Notice how much clearer this is compared to using constructor functions:
classkeyword clearly shows we're defining a template for objectsconstructormethod is clearly markedextendsshows inheritance relationshipssuper()calls the parent constructor- Methods are defined directly in the class body
The Prototype Chain
The extends keyword creates a prototype chain behind the scenes:
js
// This class hierarchy:
class Shape { }
class Square extends Shape { }
const square = new Square();
// Creates this prototype chain:
square → Square.prototype → Shape.prototype → Object.prototypeUsing super()
When extending a class:
- You must call
super()in the constructor before usingthis - If you don't define a constructor, a default one that calls
super()is created super()must be called with any parameters the parent constructor expects
js
class Shape {
constructor(colour) {
this.colour = colour;
}
}
class Square extends Shape {
constructor(colour, size) {
super(colour); // Must call super() first
this.size = size; // Now we can use this
}
}
// This would cause an error:
class BadSquare extends Shape {
constructor(colour, size) {
this.size = size; // Error: Must call super() first
super(colour);
}
}Overriding Methods
Child classes can override (replace) methods from their parent class:
js
class Animal {
speak() {
return "Some sound";
}
}
class Dog extends Animal {
speak() { // Override the parent's speak method
return "Woof!";
}
}
class Cat extends Animal {
speak() {
return "Meow!";
}
}
const dog = new Dog();
console.log(dog.speak()); // "Woof!"You can also call the parent's version of a method using super:
js
class Bird extends Animal {
speak() {
return super.speak() + " Tweet!"; // Call parent method
}
}
const bird = new Bird();
console.log(bird.speak()); // "Some sound Tweet!"Private Fields in Inheritance
Private fields (using the # prefix) are truly private in JavaScript - they are only accessible within the class where they are defined. This is different from languages like C++, where private members are accessible by friend classes and protected members are accessible by derived classes.
js
class BankAccount {
#balance = 0; // Private field
constructor(initialBalance) {
this.#balance = initialBalance;
}
getBalance() {
return this.#balance;
}
}
class SavingsAccount extends BankAccount {
#interestRate = 0.05; // Private to SavingsAccount
addInterest() {
// Can't access parent's #balance directly
// this.#balance *= (1 + this.#interestRate); // Error!
// Must use public methods to interact with parent's private fields
const currentBalance = this.getBalance();
return currentBalance * (1 + this.#interestRate);
}
}
const account = new SavingsAccount(1000);
console.log(account.getBalance()); // 1000
console.log(account.addInterest()); // 1050
// console.log(account.#balance); // Error: Cannot access private field
// console.log(account.#interestRate);// Error: Cannot access private fieldUnlike C++'s protected members, private fields in JavaScript are truly private and cannot be accessed by child classes. To share private data between classes, you must use public methods like getters and setters. If you need protected members (accessible by child classes but not from outside), TypeScript adds support for this with the protected keyword.
Static Members
Static methods and properties are inherited by child classes:
js
class Vehicle {
static numberOfVehicles = 0;
static getCount() { return this.numberOfVehicles; }
}
class Car extends Vehicle {}
console.log(Car.getCount()); // Inherited static methodType Checking
The instanceof operator checks if an object belongs to a class or its inheritance chain:
js
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
const dog = new Dog();
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Cat); // falseSingle Inheritance
JavaScript only supports single inheritance - a class can only extend one parent class. While JavaScript has no built-in interface concept, TypeScript adds interfaces which provide a way to define and implement multiple contracts while maintaining single inheritance.
js
class A {}
class B {}
// This is not allowed:
// class C extends A, B {} // Error!
// Instead, use composition:
class C {
constructor() {
this.a = new A();
this.b = new B();
}
}Abstract Classes
JavaScript doesn't have built-in support for abstract classes. While there are workarounds using new.target, it's better to use TypeScript which provides proper abstract class support with the abstract keyword.
💡 Class inheritance provides a clear and structured way to share behaviour between objects. The
extendskeyword andsuper()make it much easier to understand and maintain inheritance relationships compared to the older prototype approach.
External Resources