The idea
Many things are special cases of other things. A puppy is a dog, a car is a vehicle, a manager is an employee. Inheritance models this "is a" relationship. You write a general class once, then create a more specific class that starts with everything the general one has and adds to it. The general class is the parent (or base class) and the specific one is the child (or subclass). The child reuses the parent's code instead of copying it.
The syntax
A child class names its parent after the keyword extends:
class Puppy extends Dog {
whine() {
console.log("whimper");
}
}
A Puppy object now has everything a Dog has:
- The fields.
Puppyhas aname, although onlyDogdeclares it. - The methods. A puppy can
bark(), although onlyDogdefines that method. - The parent's type. Wherever the code expects a
Dog, you can pass aPuppy. The next lessons use this.
On top of that, the child adds its own members. whine exists on Puppy only. The inheritance goes one way: a plain Dog has no whine.
A class can extend only one parent. A parent can have many children, though, and a child can itself be a parent, so chains such as Animal, Dog, Puppy are possible.
The constructor
In the lesson, Puppy has no constructor, and this is allowed. When a subclass omits it, JavaScript supplies one that passes all its arguments to the parent's constructor. So new Puppy("Buddy") runs Dog's constructor with "Buddy", and the name field gets set.
If the child needs a constructor of its own, for example to take an extra argument, it has to call the parent's constructor with super(...), and that call must come before any use of this:
class Puppy extends Dog {
age: number;
constructor(name: string, age: number) {
super(name);
this.age = age;
}
}
The compiler reports 'super' must be called before accessing 'this' in the constructor of a derived class if the order is wrong.
The exercise
The starter code defines Dog with a name field, a constructor and a bark method that prints Woof! I am followed by the name. At the bottom it creates a Puppy named "Buddy", calls bark(), then calls whine(). The Puppy class itself is missing.
- Declare the child. Between the
Dogclass and the code that usesPuppy, writeclass Puppy extends Dog. No constructor is needed. - Add the new behaviour. Define
whinein the body, and have it log"whimper". Don't redefinebark, because the puppy gets it fromDog.
class Dog {
name: string;
constructor(name: string) { this.name = name; }
bark() {
console.log("Woof! I am " + this.name);
}
}
class Puppy extends Dog {
whine() {
console.log("whimper");
}
}
const buddy = new Puppy("Buddy");
buddy.bark();
buddy.whine();
The output is:
Woof! I am Buddy
whimper
Both lines appear even though Puppy defines only one of the two methods. The checks buddy instanceof Puppy and buddy instanceof Dog are both true, since a puppy is a dog.
Common mistakes
- Declaring the class before its parent.
class Puppy extends DogneedsDogto exist already. Write the parent first. - Copying the parent's members. Pasting
barkandnameintoPuppydefeats the purpose. Whatever the child shares with the parent should stay in the parent. - Calling a child method on a parent object. A plain
Doghas nowhine.new Dog("Rex").whine()is a compile error, and if run anyway it throwsTypeError: d.whine is not a function. - Using
thisbeforesuper. In a constructor of a child class, callsuper(...)first. - Forgetting
superarguments. If the parent's constructor needsname, a child constructor must pass it along. Otherwise the field stays unset. - Using inheritance for a relationship that is not "is a". A
Doghas a name, but it is not a kind of name. Inherit only when the child really is a more specific version of the parent.
Key takeaways
class Child extends Parentgives the child all the parent's fields and methods.- The child adds its own members without changing the parent.
- A child without a constructor uses the parent's. A child with a constructor must call
super(...)before touchingthis. - A child object can be used wherever the parent type is expected.