Inheritance
An architect is asked for a house with a garage. He will not draw everything again: he takes the finished house blueprint and adds a garage to it. Everything the old blueprint had, the rooms, the windows, the kitchen, passes to the new one unchanged. If some room has to be redone, the architect marks it on the new blueprint.
In JavaScript this is how extends works: a subclass gets the base class's fields and methods and adds its own. TypeScript keeps this mechanism and checks it: the constructor's arguments, access to fields, and that a redone method really exists in the base class.
class Animal {
protected nick: string;
constructor(nick: string) {
this.nick = nick;
}
}
class Dog extends Animal {
bark(): string {
return `${this.nick}: woof`;
}
}
console.log(new Dog("Rex").bark()); // Rex: woofLet's go through the example line by line:
Animalhas the fieldnickand a constructor that fills it.class Dog extends Animalgets all of that and adds the methodbark.Doghas no constructor of its own, sonew Dog("Rex")calls the constructor ofAnimal.- The program prints
Rex: woof.
Since the constructor came from Animal, tsc checks the arguments against it. The call new Dog() without a name does not pass:
error TS2554: Expected 1 arguments, but got 0.protected in a subclass
The field nick is declared protected. The method bark is the subclass's code, so this.nick is available to it. Outside, in the line new Dog("Rex").nick, tsc reports error TS2445: a protected field is visible only to its class and its subclasses. Were the field private, even bark would not see it.
Its own constructor and super
When a subclass has a constructor of its own, it takes on duties:
- call
super(…), the base class's constructor, with its arguments;tscchecks them as it does fornew; - do it before the first use of
this: until the base part of the object is built, nothing may be written to it; - not leave the call out entirely: without
super(…)you get errorTS2377.
override
A subclass may replace a base class method with its own; this is called overriding. The keyword override before a method says it is a replacement:
class Greeter {
greet(who: string): string {
return `Hello, ${who}`;
}
}
class LoudGreeter extends Greeter {
override greet(who: string): string {
return super.greet(who).toUpperCase();
}
}
const g: Greeter = new LoudGreeter();
console.log(g.greet("Anna")); // HELLO, ANNAoverride greetreplaces the methodgreetfromGreeter. Its parameter and result types must fit the base class's method.super.greet(who)calls the original method ofGreeter, andLoudGreeterturns its result into upper case.- The variable
ghas the typeGreeter, while the object in it is aLoudGreeter. - The program prints
HELLO, ANNA.
override protects you from typos. If you write override gret(…), tsc notices that the base class has no such method:
error TS4117: This member cannot have an 'override' modifier because it is not declared in the base class 'Greeter'. Did you mean 'greet'?Without override such a typo would pass silently: the class would get a new method gret, and greet would stay the old one. By default writing override is not required. The --noImplicitOverride flag makes it required: then tsc reports every replacement without override.
A subclass fits the base type
In the example above a LoudGreeter object sits in a variable of type Greeter, and tsc allows it. Here the blueprint picture stops working: an architect may knock out a room of the old blueprint, while a subclass cannot remove anything. Everything Greeter has, LoudGreeter has too, so its object fits wherever a Greeter is expected.
Which method does g.greet("Anna") call? The method of the object itself, that is, LoudGreeter's. The variable's type decides something else: what tsc allows you to call. Through g only the members of Greeter are available, and a new method of the subclass cannot be called through it: that is error TS2339.
Interviewers ask this often: when a subclass object sits in a variable of the base type, which class's method runs? The answer: the subclass's, because the object picks the method, not the variable's type.
Common mistake
In the subclass's constructor its own field was written before super was called:
class Animal {
protected nick: string;
constructor(nick: string) {
this.nick = nick;
}
}
class Dog extends Animal {
breed: string;
constructor(nick: string, breed: string) {
this.breed = breed; // ✗
super(nick);
}
}error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class."super must be called before this is used in a subclass's constructor." JavaScript does not allow it either and throws a ReferenceError at run time. Swap the two lines: first super(nick), then this.breed = breed.
Key points
extendspasses fields and methods to a subclass, andtscchecks the arguments ofnewagainst the constructor the subclass got.- A subclass's own constructor calls
super(…)before the firstthis. overridemarks a method replacement, andtscchecks that the base class has such a method.- A subclass object fits the base class's type, and the method of the object itself is called.