Skip to main content

The extends keyword

extends creates a child class and links it to the parent class through prototypes: instances of the child receive the parent methods, and the child class itself receives the parent static members. Together with super() this is the complete inheritance mechanism of ES6 classes.

Theory

TL;DR

  • class B extends A {} performs two links: B.prototype.__proto__ = A.prototype and Object.setPrototypeOf(B, A).
  • The first link gives inheritance of instance methods, the second gives inheritance of static methods and fields.
  • If the child declares its own constructor, it must call super() before the first access to this.
  • An overridden method can call the parent implementation through super.methodName().
  • To the right of extends you may put any expression that returns a constructor: a class, a constructor function, or the result of a factory call.

Quick example

javascript
class Animal { speak() { console.log("The animal makes a sound"); } } class Dog extends Animal { bark() { console.log("Woof-woof!"); } } const dog = new Dog(); dog.speak(); // "The animal makes a sound", the method is inherited dog.bark(); // "Woof-woof!"

Dog inherits every method of Animal, because lookup walks the chain Dog.prototype, then Animal.prototype, then Object.prototype.

What extends does under the hood

When you write:

javascript
class B extends A {}

the engine performs the equivalent of two operations:

javascript
B.prototype.__proto__ = A.prototype; // instance methods Object.setPrototypeOf(B, A); // static methods and fields

Two consequences follow:

  • instance methods (B.prototype.method) are looked up in A.prototype when they are missing from B.prototype;
  • static members (A.staticMethod) are reachable through B as well.

So extends builds two different chains at once. For an instance:

javascript
// instance of B -> B.prototype -> A.prototype -> Object.prototype -> null

And a separate chain for the classes themselves, which are function objects:

javascript
// B -> A -> Function.prototype -> Object.prototype -> null

The child constructor and super()

A child class can call the parent constructor through super().

javascript
class Animal { constructor(name) { this.name = name; } } class Dog extends Animal { constructor(name, breed) { super(name); // calls the Animal constructor this.breed = breed; } } const dog = new Dog("Rex", "shepherd"); console.log(dog.name); // "Rex" console.log(dog.breed); // "shepherd"

Important:

  • if you declare your own constructor in the child, calling super() is mandatory before any use of this;
  • without it you get ReferenceError: Must call super constructor before accessing 'this';
  • if there is no explicit constructor, the engine supplies constructor(...args) { super(...args); } itself.

Overriding methods and super.methodName()

A parent method can be overridden while still calling the old implementation through super.methodName().

javascript
class Animal { speak() { console.log("The animal speaks"); } } class Dog extends Animal { speak() { super.speak(); // calls the parent implementation console.log("The dog barks"); } } new Dog().speak(); // The animal speaks // The dog barks

Inheriting static methods and fields

If the parent has static members, they are inherited too:

javascript
class Animal { static category = "Animals"; static info() { console.log("This is the base class for animals"); } } class Dog extends Animal {} console.log(Dog.category); // "Animals" Dog.info(); // "This is the base class for animals"

This works precisely because of the second link: the lookup for Dog.category does not find it on Dog itself and climbs up to Animal.

Inheriting from functions and dynamic expressions

You can inherit not only from classes but also from plain constructor functions:

javascript
function Vehicle(name) { this.name = name; } Vehicle.prototype.drive = function () { console.log(`${this.name} is driving`); }; class Car extends Vehicle {} const car = new Car("BMW"); car.drive(); // "BMW is driving"

extends works with any value that has a prototype, not necessarily with a class.

The expression on the right is evaluated when the class is declared, so the base class can be chosen dynamically:

javascript
function getBaseClass(type) { return type === "cat" ? class Animal { speak() { console.log("Meow"); } } : class Animal { speak() { console.log("Woof"); } }; } class Pet extends getBaseClass("cat") {} new Pet().speak(); // "Meow"

This is a powerful technique: mixins, adapters and model factories all rest on it.

A summary of what extends gives you:

What extends doesDescription
Builds the prototype chainChild.prototype inherits from Parent.prototype
Lets you call the parent constructorthrough super()
Lets you call parent methodsthrough super.methodName()
Inherits static methods and fieldsChild.staticMethod()
Works not only with classesbut with constructor functions too
Accepts dynamic expressionsclass X extends getBaseClass()

Common mistakes

  • Touching this in the child constructor before calling super(). That is always a ReferenceError, not undefined.
  • Believing that statics are not inherited. In fact Dog.info() works with no extra code.
  • Trying to inherit from an arrow function or from a plain object: they have no prototype, so you get a TypeError.
  • Calling the parent method as Animal.prototype.speak.call(this) instead of super.speak(). It works, but it breaks as soon as the hierarchy changes.
  • Confusing the two chains: Dog.__proto__ is Animal, while Dog.prototype.__proto__ is Animal.prototype.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.