Modern JavaScript: Extending Classes
Welcome to the Extending Classes lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
JavaScript classes can extend (inherit from) other classes by declaring
class MySubclass extends MySuperclass. The subclass will have all of the properties and methods of the superclass, and can also define its own properties and methods. (You might hear these referred to as "child" and "parent" classes.)>
class Animal {constructor(name) {this.name = name;}}class Cat extends Animal {}new Cat('Ms. Fluff').name;Result:
'Ms. Fluff'
The superclass and subclass can define their own separate constructors. That lets us centralize shared setup code in the superclass, avoiding duplication in its subclasses. The subclass' constructor calls the superclass' constructor with
super(), passing whatever arguments the superclass' constructor requires.>
class Animal {constructor(name) {this.name = name;}}class Cat extends Animal {constructor(name) {super(name + ' the cat');}}new Cat('Ms. Fluff').name;Result:
'Ms. Fluff the cat'
>
class Animal {constructor(legCount) {this.legCount = legCount;}canWalk() {return this.legCount > 0;}}class Worm extends Animal {constructor() {super(0);}}class Dog extends Animal {constructor() {super(4);}}[new Worm().canWalk(), new Dog().canWalk()];Result:
[false, true]
If the subclass has a constructor, it has to call
super()in its constructor before accessing anythisproperties. Otherwise,newing the subclass will cause an error. This rule holds even when the superclass doesn't actually define a constructor; we still have to callsuper!(You can type
errorwhen a code example will throw an error.)>
class Animal { }class Cat extends Animal {constructor(name) {this.name = name;}}new Cat('Ms. Fluff').name;Result:
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
We can use
instanceofto check for whether an object is an instance of a given class.>
class Cat {}const cat = new Cat();cat instanceof Cat;Result:
true
>
class Cat {}'cat' instanceof Cat;Result:
false
>
class Cat {}true instanceof Cat;Result:
false
instanceofunderstands subclasses:x instanceof Yis true when x is an instance of any subclass of Y.>
class Animal { }class Cat extends Animal { }const cat = new Cat();cat instanceof Animal;Result:
true
We can inherit from classes that inherited from other classes, and so on; there's no limit.
>
class Animal {constructor(legCount) {this.legCount = legCount;}canWalk() {return this.legCount > 0;}}class Dog extends Animal {constructor(runSpeedKmPerHour) {super(4);this.runSpeedKmPerHour = runSpeedKmPerHour;}}class Greyhound extends Dog {constructor() {super(65);}}class Pug extends Dog {constructor() {super(5);}}[new Greyhound().canWalk(),new Greyhound().runSpeedKmPerHour,new Pug().canWalk(),new Pug().runSpeedKmPerHour,];Result:
[true, 65, true, 5]
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
new Pug() instanceof Dog;Result:
true
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
new Pug() instanceof Animal;Result:
true
Subclasses can define properties or methods that already exist on the superclass. When that happens, the subclass' version "overrides", or replaces, the superclass' version.
>
class Bird {speak() {return 'chirp';}}class Crow extends Bird {speak() {return 'CAW';}}[new Bird().speak(), new Crow().speak()];Result:
['chirp', 'CAW']
Here's a code problem:
Define an
Adminclass that inherits fromUser. Its constructor should callsuper, passing along the relevant arguments. It should also set the admin'sisAdminflag totrue.class User {constructor(name, email) {this.name = name;this.email = email;this.isAdmin = false;}}class Admin extends User {constructor(name, email) {super(name, email);this.isAdmin = true;}}const admin = new Admin('Amir', 'amir@example.com');[admin.name, admin.email, admin.isAdmin];- Goal:
['Amir', 'amir@example.com', true]
- Yours:
['Amir', 'amir@example.com', true]
Some languages have "multiple inheritance": they allow a class to inherit from multiple other classes at once. Python and C++ are examples. In JavaScript, there is no syntax for multiple inheritance. Trying to inherit from two classes causes an error.
>
class Alive { }class Animal { }class Dog extends Alive, Animal { }Result:
Finally, a note on when inheritance is appropriate. Object-oriented programming was very popular in the 1990s, sometimes promoted with an almost religious fervor. That fervor has died down now. We recommend viewing classes, and especially inheritance, as just another tool. Like any tool, it's important not to reach for them when they're not needed. Here's an example from React JS, a popular JavaScript UI library.
In older versions of React, components were defined using classes:
class MyComponent extends React.Component. This made a lot of sense, becauseReact.Componentdefines many properties and methods that our components will want to use. It setsthis.propsso a component can easily see the data sent to it from parent components. It definesthis.setStateso a component can change its internal state values easily. And so on.That class syntax has since been superseded by a very clever mechanism called React hooks. With hooks, the classes disappear and we just write functions. Components written with hooks are noticeably shorter, and the consensus is that hooks usually lead to components that are easier to read and reason about.
React hooks' success isn't an argument that "classes are bad". But it does show us that, even in cases where classes model a problem well, there is sometimes an even better model. Execute Program itself contains a number of classes:
User,Graph(a graph data structure),Parser(a parser for our course definition markup language), etc. But most of the application logic is contained in regular functions, not in classes.