Q1What happens when you call new on an EBook that assigns to this.fileSize before super()?
Inheritance — extends and super
Build a new class on top of an existing one with extends. Covers super() in a child constructor, overriding, and super.methodName().
Say an online bookstore needs a class for e-books alongside its Book class for print books. The only differences are that e-books can be downloaded and don't have a shipping fee, yet with what you know so far, you'd end up rewriting the parts that handle the title and price as well.
This article covers extends, which builds on an existing class, and super, which calls code in the class you built on.
Reusing Code Without Copying It — extends
E-books also need a constructor that takes a title and a price, and a describe method that returns "title: price yen". If you copy the body of Book into a new e-book class, the same lines exist in two places. If you later change the display format and forget to update one copy, different screens end up showing different formats.
Inheritance (a way to let another class use an existing class's constructor and methods) is written as class EBook extends Book. Book is called the parent class, and EBook the child class.
The extends Error you wrote in the throw and custom errors article is inheritance too.
class Book {
constructor(title, price) {
this.title = title;
this.price = price;
}
describe() { return `${this.title}: ${this.price} yen`; }
}
// Builds on Book and adds a method only e-books have
class EBook extends Book {
download() { return `Downloading ${this.title}`; }
}
const ebook = new EBook("Stats 101", 2200); // The arguments go to Book's constructor
console.log(ebook.describe()); // Stats 101: 2200 yen
console.log(ebook.download()); // Downloading Stats 101
const book = new Book("Stats 101", 2640);
console.log(typeof book.download); // undefined (a parent class doesn't have its child's methods)
Inside ebook.describe(), this is ebook, so the this.title written in Book reads the title you passed to new EBook. And it was Book's constructor, receiving those arguments, that put the title on ebook in the first place.
Adding Values in a Child Class — super() and the constructor
Now suppose e-books also need to hold their file size, fileSize, in addition to the title and price. To accept the file size, EBook needs its own constructor. But once you write one, Book's constructor is no longer called automatically, and if you leave it at that, new throws a ReferenceError.
super() (a way to call the parent class's constructor from inside a child class's constructor) takes the arguments to pass to the parent, as in super(title, price);.
In a child class, this is set up by the parent's constructor during super(), so call it before any line that uses this.
class Book {
constructor(title, price) {
this.title = title;
this.price = price;
}
}
class EBook extends Book {
constructor(title, price, fileSize) {
// Writing this.fileSize = fileSize; above this line makes new throw a ReferenceError
super(title, price); // Book's constructor sets title and price
this.fileSize = fileSize; // Then add the value only EBook has
}
}
const ebook = new EBook("Stats 101", 2200, 12);
console.log(ebook.title); // Stats 101
console.log(ebook.fileSize); // 12
console.log(Object.keys(ebook).join(", ")); // title, price, fileSize
The keys come out in the order title, price, fileSize because Book set its values first, inside super(). The top row fails with ReferenceError: Must call super constructor in derived class before accessing 'this' …, where "derived class" means the child class.
A Child Class Can't Read the Parent's # Fields
If Book has a private field #price, super() still sets its value, but writing this.#price in the body of EBook is a SyntaxError. A # name can only be used inside the class that declares it, so a child class has to read it through a getter defined in the parent.
Changing the Shipping Fee in a Child Class — Overriding
If you add a shippingFee method to Book, e-books get the same shipping fee as print books. You could make it 0 yen for e-books only by checking the book type with if inside Book, but then the parent class needs another branch every time you add a child class.
Overriding (writing a method in a child class with the same name as one in the parent, so the child's version runs for the child's instances) just means writing shippingFee again in the body of EBook. When you call a method, JavaScript looks for the name starting in the class that created the instance, and moves on to the parent class if it isn't there.
class Book {
constructor(title, price) {
this.title = title;
this.price = price;
}
describe() { return `${this.title}: ${this.price} yen`; }
shippingFee() { return this.price >= 3000 ? 0 : 500; } // Free shipping at 3000 yen or more
}
class EBook extends Book {
shippingFee() { return 0; } // Overridden under the same name (nothing to ship)
}
const book = new Book("Stats 101", 2640);
const ebook = new EBook("Stats 101", 2200);
console.log(book.shippingFee()); // 500
console.log(ebook.shippingFee()); // 0 (EBook's shippingFee runs)
console.log(ebook.describe()); // Stats 101: 2200 yen (Book's describe runs)
EBook's shippingFee doesn't use Book's calculation, so even if you change Book's shipping rules, e-books stay at 0 yen. The next section covers how to call the parent's code and then add only what's different.
Adding to the Parent's Code — super.methodName()
Next, suppose you want e-book descriptions to end with "(e-book, 12 MB)". If you override describe in EBook and copy over the code that builds "title: price yen", then the next time you change Book's display format, the e-book descriptions will be left in the old format.
With super.methodName() (a way to call a method defined in the parent class from inside a child class's method), super.describe() returns the value of Book's describe. The child class only has to add the e-book details to that string.
class Book {
constructor(title, price) {
this.title = title;
this.price = price;
}
describe() { return `${this.title}: ${this.price} yen`; }
}
class EBook extends Book {
constructor(title, price, fileSize) {
super(title, price);
this.fileSize = fileSize;
}
// Adds the e-book details to the return value of Book's describe
describe() { return `${super.describe()} (e-book, ${this.fileSize} MB)`; }
}
const ebook = new EBook("Stats 101", 2200, 12);
console.log(ebook.describe()); // Stats 101: 2200 yen (e-book, 12 MB)
super.describe() is an expression that evaluates to Book's return value, so if you'd rather put the e-book details first, just move it within the template literal. The table below summarizes the inheritance syntax used in this article and what runs for each.
| Syntax | Where It Goes | What Runs |
|---|---|---|
| class EBook extends Book | Class declaration | Inherits Book's constructor and methods |
| super(title, price) | Child constructor, before this | Book's constructor |
| Method with the parent's name | Child class body | EBook's version, for EBook instances |
| super.describe() | Inside a child method | Book's describe |
Without super, the Method Calls Itself Forever
If you write this.describe() instead of super.describe() in the child class's describe, the method keeps calling itself until it throws RangeError: Maximum call stack size exceeded. To call the parent's code from a method you've overridden, use super..
Knowledge Check
Answer each question one by one.
Q2If both Book and EBook have a shippingFee method, which one runs when you call ebook.shippingFee()?
Q3Inside EBook's describe, how do you get the return value of Book's describe?