Topic 7.7
Inheritance with extends and super
In one line
class Child extends Parent makes a class that starts with everything Parent has and adds or changes parts; super(...) runs the parent's constructor, super.method() calls the parent's version of a method you override, and instanceof checks the whole family line.
Think of it like this
A family recipe passed down. Grandma's biryani recipe is the parent. Your mum uses it as a base but adds extra spice: that's a child recipe. She didn't rewrite the whole recipe; she said "do grandma's recipe, then add chilli". When someone asks "is this grandma's biryani?", the answer is "yes, a version of it". extends is "based on", super is "do grandma's step first", and instanceof is "is this a version of that recipe?".
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Inheritance
- One class reusing another class's properties and methods, and adding or changing some.
- Superclass / parent
- The class being extended, like
Animalinclass Dog extends Animal. - Subclass / child
- The class that extends another, like
Dog. - extends
- The keyword that makes a class a subclass of another and links their prototype chains.
- super(...)
- In a subclass constructor, the call that runs the parent's constructor. It must come before using
this. - super.method()
- Calls the parent's version of a method from inside the child's methods.
- Override
- Define a method in the child with the same name as one in the parent, replacing it for child instances.
- is-a relation
- When one thing is truly a kind of another (a dog is an animal). A good reason to use inheritance.
Step by step
01Start with a parent class
Animal stores a name and has a method and a static factory.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return this.name + " makes a sound";
}
static create(name) {
return new this(name);
}
}02Extend it
Dog calls super(name) first, so Animal sets up name. Only then can it use this for its own breed.
class Dog extends Animal {
constructor(name, breed) {
super(name); // Animal's constructor runs here
this.breed = breed;
}
}03Override and extend a method
Dog defines its own speak, which wins over Animal's for dogs. Inside, super.speak() still reaches the parent's version, so we add to it instead of repeating it.
class Dog extends Animal {
// ...constructor as before
speak() {
return super.speak() + ", then barks";
}
}
new Dog("Rex").speak(); // "Rex makes a sound, then barks"04See the two chains extends builds
Instances find methods along the bottom row; the classes themselves find static members along the top row.
05Know the order of construction
The parent finishes completely, including its fields, before the child's fields are created.
Try it yourself
- 1
Add a third level
In the first example, add
class Puppy extends Dog { speak() { return super.speak() + " (tiny)"; } }and lognew Puppy("Bit", "Pug").speak(). Predict the whole sentence first. - 2
Forget super
In the first example, comment out
super(name);. Predict what the console shows, then read the error carefully. - 3
Fix the trap
In the last example, move the log out of
Base's constructor into a methodinit()and callc.init()afternew Child(). Predict the output.
Code & diagrams
`name` is set by Animal, then the Dog field `tricks`, then `breed`. `Dog.create` is inherited from Animal, and `new this` builds a Dog because it was called as `Dog.create`.
Preview
Console
Nothing logged yet.Expected console output
Dog { name: 'Rex', tricks: [ 'sit' ], breed: 'Labrador' }
Rex makes a sound, then barks
Milo makes a sound
true true false
Dog { name: 'Bolt', tricks: [], breed: undefined }The static field `kind` is reached through the class chain. Calling `Shape.prototype.area` directly skips the override.
Preview
Console
Nothing logged yet.Expected console output
Square -> Shape -> Object -> null
true
true shape
9 0`map` on a Stack returns a Stack: built-in array methods create new results using the subclass. The custom error keeps the message and stack trace from `Error`.
Preview
Console
Nothing logged yet.Expected console output
ValidationError: Email needs an @
true true email
3 3 true
trueThe overridden `describe` already runs during `super()`, but Child's fields are only created after `super()` returns, so `label` is still `undefined`.
Preview
Console
Nothing logged yet.Expected console output
Base constructor sees: Child with undefined
Later: Child with child fieldBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use this before super
In Dog's constructor, put this.breed = breed; above super(name);.
Break #2
Forget to set the error's name
Remove this.name = "ValidationError"; from the custom error and log the caught error.
Myth vs fact
Myth
Every subclass needs a constructor.
Fact
If you don't need extra setup, leave it out. The default derived constructor passes all arguments to super.
Myth
super.method() calls the method on a separate parent object.
Fact
There is only one object. super.method() finds the parent's function on the parent prototype and calls it with this = the current instance.
Myth
Inheritance is the main way to reuse code.
Fact
It is one way, best for real is-a relations and shallow trees. Composition and plain functions are often simpler and more flexible (Topic 7.8).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Methods record their home object (
[[HomeObject]]) when defined, andsuperlooks up from that object's prototype, not fromthis. So copying a method that usessuperto another object (for example withObject.assign) keeps calling the original parent. That's whysuperonly works in methods, not in functions assigned later. - ▸
Built-in subclassing works because the parent constructor (
Array,Error,Map,Promise) allocates the exotic object, andnew.targettells it to link to your subclass's prototype. Array methods likemapandfilteruseconstructor[Symbol.species]to choose the result's class; overridestatic get [Symbol.species]() { return Array; }if you want plain arrays back. - ▸
A field in a subclass with the same name as a parent accessor is defined as an own data property on the instance, hiding the parent's getter/setter, and a parent field is re-defined by the child field after
super(). Override behaviour with methods and accessors, not with fields of the same name. - ▸
Custom elements are the most common real-world
extendsin the browser:class UserCard extends HTMLElement { connectedCallback() { ... } }pluscustomElements.define("user-card", UserCard). They must callsuper()and must not add attributes or children in the constructor.
Remember this
- 1
Inheritance means one class reuses and extends another.
class Dog extends Animal { ... }makesDoga subclass (child) of the superclass (parent)Animal. EveryDoginstance can useAnimal's methods, andDogcan add new methods or override (replace) existing ones by defining a method with the same name. Use it for a real "is-a" relation: a dog is an animal; a validation error is an error. - 2
extendswires up two prototype chains (Topic 7.3). Instances:Dog.prototype→Animal.prototype→Object.prototype, sorex.speakis looked up onDog.prototypefirst and then onAnimal.prototype. Classes:Dog→Animal, so static members are inherited too (Dog.create()works ifAnimalhasstatic create). - 3
In a subclass, the **constructor must call
super(...)before usingthis. The reason: in a derived class, the parent** creates the object. Untilsuper()returns there is no object, so touchingthisthrows a ReferenceError.super(name)passes arguments to the parent constructor. If you don't write a constructor at all, JavaScript usesconstructor(...args) { super(...args); }for you. - 4
In any method, **
super.method(...)** calls the parent's version, withthisstill the current instance. That lets you extend behaviour instead of copying it:speak() { return super.speak() + ", then barks"; }.superworks only in methods written in a class or object literal body (it is fixed to where the method was written, not tothis). - 5
**
instanceof** checks the whole chain, so aDogisinstanceof Dog,instanceof Animalandinstanceof Object. You can extend built-ins:class ValidationError extends Errorgives you errors with a proper stack trace and your own extra fields, andclass Stack extends Arraygives array behaviour (includinglength) plus your methods. Setthis.namein custom errors so the console and logs show the right type. - 6
Construction order matters: (1) the derived constructor starts; (2)
super()runs the parent's field initialisers and constructor body; (3) the child's fields are initialised; (4) the rest of the child constructor runs. So a parent constructor that calls an overridden method sees the child's method before the child's fields exist. Keep parent constructors simple, and keep hierarchies shallow: one or two levels. Deep trees become hard to change, which leads to Topic 7.8.
Explain it without notes
What does class B extends A set up, in terms of prototypes?
Why must a subclass constructor call super() before using this?
How do you override a method but still use the parent's behaviour?
What is the construction order of a subclass instance, and what bug can it cause?
Practice
Write class Vehicle with constructor(wheels) and describe() returning "A vehicle with N wheels", and class Bike extends Vehicle that passes 2 to super and overrides describe() to add " and a bell" using super.describe().
Write class NotFoundError extends Error that takes a resource name, sets the message to "<resource> not found", sets name, and stores status = 404. Throw and catch it, and log the error and its status.
Write class SortedList extends Array with an add(value) method that inserts the value and keeps the array sorted ascending (numbers). Add 5, 1, 3 and log the array with Array.from.
Trade-offs
- ↔
Inheritance removes duplication for real is-a families and lets code treat all subclasses the same way, but couples child to parent: a change in the parent can break every child (the fragile base class problem).
- ↔
Extending built-ins like
Erroris clean and recommended; extendingArrayorMapworks but ties your class to their whole API, so wrapping one in a field is often safer.
Done when you can
I can write a subclass with
extendsand callsuper(...)first in its constructor.I can override a method and reuse the parent's version with
super.method().I can explain the two prototype chains
extendscreates, including static inheritance.I can write custom error classes with a proper
name.I know the construction order and avoid calling overridable methods in constructors.