JavaScript Class Inheritance
Learn JavaScript class inheritance with extends and super: the prototype chain, overriding methods and properties, and inheriting static methods.
Introduction to JavaScript Class Inheritance
Class inheritance is a fundamental concept in object-oriented programming that allows one class to inherit properties and methods from another class. In JavaScript, class inheritance is implemented using the extends keyword, providing a way to create a class as a derived class that inherits from a base class.
For more information about the basic syntax, see JavaScript: Classes and Basic Syntax.
This chapter covers how to create derived classes, override methods and properties, call into the parent with super, extend built-in classes like Array, and inherit static methods — plus the prototype chain that makes it all work under the hood.
How Inheritance Works: The Prototype Chain
The class keyword is syntactic sugar over JavaScript's prototypal inheritance. When you write class Circle extends Shape, the engine wires up two links:
Circle.prototype.__proto__ === Shape.prototype— so instance methods resolve up the chain.Circle.__proto__ === Shape— so static methods are inherited too.
When you read a property or call a method on an object, the engine looks at the object itself first. If it isn't found there, it walks up the prototype chain — to Circle.prototype, then Shape.prototype, then Object.prototype, then null — stopping at the first match. This is exactly why an instance of Circle can call a method defined on Shape.
Object.getPrototypeOf(obj) returns the next link in the chain (the standard, preferred way to inspect it). The legacy obj.__proto__ accessor points at the same object. Understanding this chain explains everything below: overriding works because a closer prototype shadows a farther one, and super works because it explicitly skips to the parent's prototype.
Creating a Derived Class
To create a class that inherits from another, we use the extends keyword:
In this example, Circle extends Shape, meaning it inherits Shape's properties and methods, and at the same time providing methods of its own. Note that Circle does not define its own constructor. In JavaScript, derived classes without an explicit constructor automatically call super() with the same arguments passed to the derived class constructor.
Overriding Methods
Derived classes can override methods of their base classes to provide behavior specific to the subclass.
Here, Circle overrides the print method to reflect its specific type.
Calling Parent Methods with super
You can also call a parent class's method from within the derived class using super.methodName(). This is useful when you want to extend the parent's behavior rather than completely replace it.
Here, super.print() executes the parent's logic before adding the subclass-specific output.
A Realistic Override: Computing Area
Overriding shines when each subclass needs genuinely different behavior. Here a base Shape defines the shared interface, and each subclass overrides area() with its own real calculation. Calling area() on a Circle resolves to Circle.prototype.area, which shadows the base version.
Notice that describe() is defined only on Shape, yet it calls this.area() and gets the subclass implementation. That is the prototype chain at work: this always refers to the actual instance, so method lookup starts from Circle or Rectangle.
Overriding Properties and Reading super.prop
super is not limited to methods — you can read a property defined on the parent prototype with super.prop, which is handy when a subclass wants to build on a parent's getter rather than replace it.
Accessing Parent Constructor: super keyword
When a class extends another class, the constructor function of the derived class needs to call the parent's constructor using super() before it can use this. Here’s how super is used in constructors to ensure the parent class is initialized:
Why super() Must Run Before this
In a derived class, the instance object is not created until super() runs — that is the parent constructor's job. Until then, this is in an uninitialized state, so touching it (reading, assigning, or even returning the object implicitly) throws a ReferenceError. This is a language rule, not a style preference.
A related rule: if a derived class defines a constructor, it must call super() somewhere before it finishes, or the same ReferenceError is thrown. (A derived class with no explicit constructor is fine — JavaScript inserts constructor(...args) { super(...args); } for you.) Once super() returns, this is fully initialized and ready to use.
Inheriting Static Methods
Static members belong to the class itself, not to instances. Because extends also links Circle.__proto__ to Shape, derived classes inherit static methods and can call them directly. For more on declaring them, see JavaScript Static Properties and Methods.
Inside a static method, this refers to the class it was invoked on, so Shape.create called as Circle.create constructs a Circle.
Multi-Level Inheritance
Chains can be more than two levels deep. Method lookup simply walks further up the prototype chain, and super always refers to the prototype one level above the class where the method is defined.
Extending Built-in Classes
You can extend native classes such as Array, Error, or Map to create specialized versions that keep all built-in behavior while adding your own.
The Symbol.species gotcha. Methods like map, filter, and slice return a new collection. By default they return an instance of your subclass (MyArray), not a plain Array — which is usually fine, but can surprise code that expects a real Array. You can opt back into plain arrays by overriding the static Symbol.species getter.
For combining behavior from multiple sources (JavaScript has no multiple inheritance), see JavaScript Mixins.
Summary
Key takeaways:
- Use
extendsto derive a class; it links both the prototype chain (instance methods) and the class itself (static methods) to the parent. - Method and property lookup walks the prototype chain, stopping at the first match — which is why a closer definition overrides a farther one. Inspect the chain with
Object.getPrototypeOf(). - In a derived constructor, call
super()beforethis. Untilsuper()runs, the instance is uninitialized and any use ofthisthrows aReferenceError. - Reach parent behavior explicitly with
super.method()orsuper.prop, even when you've overridden it. - You can extend built-ins like
Array— just watch theSymbol.speciesbehavior when methods return new collections.
Next steps:
- JavaScript Prototypal Inheritance — the mechanism classes are built on.
- JavaScript Static Properties and Methods — declaring and inheriting static members.
- JavaScript Mixins — reusing behavior across unrelated classes without a single inheritance chain.