Command Palette

Search for a command to run...

Hectal
PHASE 7Intermediate ES2015+ ~26 min· topic 7 of 8

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 Animal in class 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.

animals.jswhole filejavascript
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.

animals.jswhole filejavascript
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.

animals.jswhole filejavascript
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.

See the two chains extends buildsdiagram
Rendering diagram…

05Know the order of construction

The parent finishes completely, including its fields, before the child's fields are created.

Know the order of constructiondiagram
Rendering diagram…

Try it yourself

  1. 1

    Add a third level

    In the first example, add class Puppy extends Dog { speak() { return super.speak() + " (tiny)"; } } and log new Puppy("Bit", "Pug").speak(). Predict the whole sentence first.

  2. 2

    Forget super

    In the first example, comment out super(name);. Predict what the console shows, then read the error carefully.

  3. 3

    Fix the trap

    In the last example, move the log out of Base's constructor into a method init() and call c.init() after new Child(). Predict the output.

Code & diagrams

A subclass that overrides and extends New tab

`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

Press Run to see the page.

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 two chains extends builds New tab

The static field `kind` is reached through the class chain. Calling `Shape.prototype.area` directly skips the override.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Square -> Shape -> Object -> null
true
true shape
9 0
Extending built-ins: a custom Error and an Array New tab

`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

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

ValidationError: Email needs an @
true true email
3 3 true
true
The construction-order trap New tab

The overridden `describe` already runs during `super()`, but Child's fields are only created after `super()` returns, so `label` is still `undefined`.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Base constructor sees: Child with undefined
Later: Child with child field

Break 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);.

terminal
$ Console
── what you'll see ──
Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

Break #2

Forget to set the error's name

Remove this.name = "ValidationError"; from the custom error and log the caught error.

terminal
$ Console
── what you'll see ──
Error: Email needs an @

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, and super looks up from that object's prototype, not from this. So copying a method that uses super to another object (for example with Object.assign) keeps calling the original parent. That's why super only works in methods, not in functions assigned later.

  • ▸

    Built-in subclassing works because the parent constructor (Array, Error, Map, Promise) allocates the exotic object, and new.target tells it to link to your subclass's prototype. Array methods like map and filter use constructor[Symbol.species] to choose the result's class; override static 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 extends in the browser: class UserCard extends HTMLElement { connectedCallback() { ... } } plus customElements.define("user-card", UserCard). They must call super() and must not add attributes or children in the constructor.

Remember this

  1. 1

    Inheritance means one class reuses and extends another. class Dog extends Animal { ... } makes Dog a subclass (child) of the superclass (parent) Animal. Every Dog instance can use Animal's methods, and Dog can 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. 2

    extends wires up two prototype chains (Topic 7.3). Instances: Dog.prototype → Animal.prototype → Object.prototype, so rex.speak is looked up on Dog.prototype first and then on Animal.prototype. Classes: Dog → Animal, so static members are inherited too (Dog.create() works if Animal has static create).

  3. 3

    In a subclass, the **constructor must call super(...) before using this. The reason: in a derived class, the parent** creates the object. Until super() returns there is no object, so touching this throws a ReferenceError. super(name) passes arguments to the parent constructor. If you don't write a constructor at all, JavaScript uses constructor(...args) { super(...args); } for you.

  4. 4

    In any method, **super.method(...)** calls the parent's version, with this still the current instance. That lets you extend behaviour instead of copying it: speak() { return super.speak() + ", then barks"; }. super works only in methods written in a class or object literal body (it is fixed to where the method was written, not to this).

  5. 5

    **instanceof** checks the whole chain, so a Dog is instanceof Dog, instanceof Animal and instanceof Object. You can extend built-ins: class ValidationError extends Error gives you errors with a proper stack trace and your own extra fields, and class Stack extends Array gives array behaviour (including length) plus your methods. Set this.name in custom errors so the console and logs show the right type.

  6. 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

01

What does class B extends A set up, in terms of prototypes?

02

Why must a subclass constructor call super() before using this?

03

How do you override a method but still use the parent's behaviour?

04

What is the construction order of a subclass instance, and what bug can it cause?

Practice

01

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().

02

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.

03

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 Error is clean and recommended; extending Array or Map works 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 extends and call super(...) 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 extends creates, 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.