Inheritance in JavaScript - extends, super, and the Prototype Chain
In JavaScript, inheritance is based on the prototype chain. Starting with ES6, the class syntax simplified this mechanism with the extends and super() keywords. In this section, we'll analyze how class inheritance actually works, including method and property inheritance, the chain connection, and the role of super.
1. Basic Syntax: extends and super()
Let's start with a simple example where one class inherits from another using extends and super().
class Animal { constructor(name) { this.name = name; }
speak() { console.log(${this.name} makes a sound); } }
class Dog extends Animal { constructor(name, breed) { super(name); // Calls the parent constructor this.breed = breed; }
speak() { super.speak(); // Calls the parent method console.log(${this.name} barks); } }
const d = new Dog('Rex', 'Labrador'); d.speak(); // Rex makes a sound // Rex barks
Explanation:
-
Dog extends Animalmeans Dog inherits from Animal's prototype -
super(name)is mandatory if we write a constructor in the child class -
super.speak()calls the parent class's method
2. How the Prototype Chain Works
When we use extends, JavaScript creates two chains: one for prototype objects and one between constructors.
console.log(Dog.prototype.__proto__ === Animal.prototype); // true
console.log(Dog.__proto__ === Animal); // true
These connections allow an instance to access both its own methods and its parent's methods through the [[Prototype]] chain.
Graphically:
d --> Dog.prototype --> Animal.prototype --> Object.prototype
3. super() vs super.method()
super() - Used inside the constructor to call the parent's constructor. Must be used before accessing this.
constructor(name) { // this.name = name; ❌ Error super(name); // ✅ }
super.method() - Used to access a parent class's method from within an instance method.
speak() { super.speak(); // Calls Animal.prototype.speak }
4. Inherited Static Methods
Static methods are also inherited. If a base class has a static method, the derived class will have it too without needing to redefine it.
class Person { static info() { return 'I am a Person'; } }
class Developer extends Person {}
console.log(Developer.info()); // I am a Person
5. Chain Overriding and Method Resolution Order
If the same method is defined in multiple levels of classes, JavaScript uses the closest method according to the chain.
class A { who() { return 'A'; } }
class B extends A { who() { return 'B'; } }
class C extends B { who() { return super.who() + '->C'; } }
const c = new C(); console.log(c.who()); // B->C
Explanation:
super.who() calls B's method because C directly inherits from B.
6. Object.assign vs extends
You can create "structural" inheritance without classes using Object.assign, but this doesn't create prototype inheritance.
const animalMethods = { speak() { console.log(`${this.name} speaks`); } };
function createDog(name) { const dog = { name }; return Object.assign(dog, animalMethods); }
This approach works but doesn't support instanceof, doesn't preserve constructor, and is less clean for large applications.
Conclusion
JavaScript's class inheritance system is built on the prototype chain, but ES6 syntax presents it as a clear OOP structure. The extends and super() keywords make inheritance more readable, though internally they work on the same prototype principles.
In the next part, we'll discuss how to implement polymorphism in JavaScript classes, override methods, and create common interfaces for different classes.