4. Inheritance
Table of Contents:
Key Concepts
Inheritance describes a relationship between two classes: a subclass that inherits methods from a superclass. Instances of the subclass can reuse and add to the methods defined in a superclass.
The
Subclass extends Superclasssyntax defines an inheritance relationship between two classesThe
superkeyword references the superclass of a given subclass:We can invoke
super()in the subclass constructor to invoke the superclass'sconstructor().We can invoke
super.method()in an overridden method to invoke the superclass's version of the method on the subclass instance.
The prototype chain describes the linked structure where each object inherits methods from a prototype, which may inherit methods from another prototype, forming a chain.
When you access a property or method, JavaScript walks up this chain until it finds what you're looking for (or reaches the end).
Inheritance
A Person and a Student
Consider this Person class.
class Person {
constructor(first, last, age) {
this.firstName = first;
this.lastName = last;
this.age = age;
}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
introduce() {
return `Hi, I'm ${this.firstName} and I'm ${this.age} years old.`;
}
}
const ada = new Person('Ada', 'Lovelace', 30);
console.log(ada.fullName()); // Ada Lovelace
console.log(ada.introduce()); // Hi, I'm Ada and I'm 30 years old.I want to make another class called Student that can do everything a Person can. In addition, Student will have some additional properties and methods.
class Student {
courses = [];
constructor(first, last, age, subject, school) {
this.firstName = first;
this.lastName = last;
this.age = age;
this.subject = subject;
this.school = school;
}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
introduce() {
return `Hi, I'm ${this.firstName} and I'm ${this.age} years old. I am studying ${this.subject} at ${this.school}.`;
}
enrollInCourse(courseName) {
this.courses.push(courseName);
}
}
const alan = new Student('Alan', 'Turing', 24, 'Computer Science', 'Marcy Lab School');
console.log(alan.fullName()); // Alan Turing
console.log(alan.introduce()); // Hi, I'm Alan and I'm 24 years old. I am studying Computer Science at Marcy Lab School.
ada.enrollInCourse('Leadership & Development');
ada.enrollInCourse('Technical Interview Prep');
console.log(ada.courses); // [ 'Leadership & Development', 'Technical Interview Prep' ]extends
extendsA Student is essentially a specific kind of Person. In computer science, we would say that "a Student is a Person" to describe this relationship.
In JavaScript, we formalize this relationship using the Subclass extends Superclass syntax:
const Person = require('./Person');
// Student is the "Subclass", Person is the "Superclass"
class Student extends Person {
}
const ada = new Student('Ada', 'Lovelace', 30);
console.log(ada.fullName()); // Ada Lovelace
console.log(ada.introduce()); // Hi, I'm Ada and I'm 30 years old.With just this extends keyword, every instance of Student "inherits" the properties and methods from the Person class.
Furthermore, instances of Student are also instances of Person.
console.log(ada instanceof Student); // true
console.log(ada instanceof Person); // truesuper
superWe don't want our Student class to just be a carbon-copy of the Person class. When we want to add new functionality to a subclass and still leverage the functionality of the superclass, we use the super keyword.
The super keyword references the superclass of a given subclass. super allows us to extend the functionality of the superclass's methods:
const Person = require('./Person');
class Student extends Person {
// A field unique to Students
courses = [];
constructor(first, last, age, subject, school) {
// Invoke the superclass constructor
super(first, last, age);
// Assign instance properties unique to Students
this.subject = subject;
this.school = school;
}
// fullName is inherited from Person
// Overriding the introduce method
introduce() {
// Invoke the Person's version of the introduce method
return `${super.introduce()}. I am studying ${this.subject} at ${this.school}.`
}
// A method unique to Student
enrollInCourse(courseName) {
this.courses.push(courseName);
}
}
const ada = new Student('Ada', 'Lovelace', 30, 'Computer Science', 'Marcy Lab School');
console.log(ada.fullName()); // Ada Lovelace
console.log(ada.introduce()); // Hi, I'm Ada and I'm 30 years old. I am studying Computer Science at Marcy Lab School.
ada.enrollInCourse('Leadership & Development');
ada.enrollInCourse('Technical Interview Prep');
console.log(ada.courses); // [ 'Leadership & Development', 'Technical Interview Prep' ]class Person {
constructor(first, last, age) {
this.firstName = first;
this.lastName = last;
this.age = age;
}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
introduce() {
return `Hi, I'm ${this.firstName} and I'm ${this.age} years old.`;
}
}
module.exports = Person;This example shows the two common usages of the super keyword:
super()— invokes the superclass constructorIn the
Studentconstructor,super()invokes thePerson's constructor which assigns the propertiesfirstName,lastName, andageto the newStudentinstance.
super.method()— invokes a method defined in the superclass. Often used to override the behavior of that method while still using some of the behavior from the superclass.The
introduce()method is overridden in theStudentclass but usessuper.introduce()to invoke theintroducemethod defined in thePersonclass.
Notice that the fullName method is inherited from the Person class and is NOT overridden. It will behave the same way for student instances and person instances.
Challenge: Professor Class
Create a class called Professor that is a subclass of Person with the following behavior:
const reuben = new Professor('Reuben', 'Ogbonna', 36, 'Software Engineering', 'Marcy Lab School');
console.log(reuben.fullName()); // Reuben Ogbonna
console.log(reuben.introduce()); // Hi, I'm Reuben and I'm 36 years old. I teach Software Engineering at Marcy Lab School.
console.log(reuben.teach('object-oriented programming')); // Hello class, today we will be learning about object-oriented programming.Then, create class called MarcyStudent that is a subclass of Student.
All instances of
MarcyStudentshould havesubjectbe set to"Software Engineering"andschoolbe set to"Marcy Lab School".It should have a static
validCoursesarray:['Leadership & Development', 'Technical Interview Prep', 'Technical Lecture']enrollInCourseshould be overridden to first check if the provided course is in thevalidCoursesarray before adding the course to thecoursesarray.
Prototype Chain: Subclasses of Subclasses
Array.prototype
Have you considered how arrays are able to use methods like push and forEach?
If we look at the "own properties" of an array (the properties defined on the array), we don't see any of its methods:
const arr = ['a', 'b', 'c'];
const arrayProperties = Object.getOwnPropertyNames(arr);
console.log(arrayProperties); // ['0', '1', '2', 'length']As we can see, the properties of an array are just the indexes for each value and the length property. So, where are those methods defined?
We get a hint if we look at the documentation for the array method push(). Notice how it names the method as Array.prototype.push?
Arrays are able to use array methods because:
Methods like
pushandforEachare defined as methods of theArrayclass and are therefore inherited by allArrayinstances. We can see those methods by looking at the properties of theArray.prototypeobject.const arrayMethods = Object.getOwnPropertyNames(Array.prototype); console.log(arrayMethods); /* [ 'length', 'constructor', 'at', 'concat', 'copyWithin', 'fill', 'find', 'findIndex', 'findLast', 'findLastIndex', 'lastIndexOf', 'pop', 'push', <--- 'reverse', 'shift', 'unshift', 'slice', 'sort', 'splice', 'includes', 'indexOf', 'join', 'keys', 'entries', 'values', 'forEach', <--- 'filter', 'flat', 'flatMap', 'map', 'every', 'some', 'reduce', 'reduceRight', 'toLocaleString', 'toString', 'toReversed', 'toSorted', 'toSpliced', 'with' ] */Whenever we create an "array literal" (using square brackets
[]), we are actually creating a new instance of theArrayclass!// these do the same thing let arr2 = ['dee', 'too']; // using "array literal" syntax let arr3 = new Array('dee', 'too'); // using the Array class constructor console.log(arr2 instanceof Array); // true console.log(arr3 instanceof Array); // true
Object.prototype and the Prototype Chain
Now, in that list of Array.prototype methods, can you find a method called hasOwnProperty?
It's not there, and yet we can invoke that method on an Array instance:
const letters = ['a', 'b', 'c'];
// .hasOwnProperty returns true if a given property name is an "own property".
console.log(letters.hasOwnProperty(1)); // true
console.log(letters.hasOwnProperty('push')); // falseThe Object.prototype.hasOwnProperty method is defined as a part of the Object class from which all classes are extended, including the Array class and the classes we've made here like Person and Student.
const ada = new Student('Ada', 'Lovelace', 30, 'Computer Science', 'Marcy Lab School');
const letters = ['a', 'b', 'c'];
console.log(ada instanceof Object);
console.log(letters instanceof Object);Since arrays are instances of the Array class which extends the Object class, we end up with what we call a prototype chain: a linked structure of objects inheriting methods from class prototypes, which can inherit methods from other class prototypes, and so on...
letters → Array.prototype → Object.prototype → null
The prototype chain is used by JavaScript to find method definitions when the method can't be found the object invoking the method.
For example, when letters.push() is invoked, JavaScript looks at letters to see if it has the definition of push. When it doesn't find it, it looks at inside of Array.prototype where it finds the definition of push.
When letters.hasOwnProperty() is invoked, JavaScript looks at letters, then Array.prototype, and then finally at Object.prototype to find the definition.
If we were to invoke a non-existent method like letters.foo(), JavaScript would look at letters, then Array.prototype, then Object.prototype and then would run out of prototypes to look at. At this point, JavaScript would determine that foo is not a function and throw a TypeError.
Quiz!
Consider the ada instance of the Student class below.
const ada = new Student('Ada', 'Lovelace', 30, 'Computer Science', 'Marcy Lab School');Consider the code below:
ada.fullName()
ada.toString()
ada.name
ada.introduce()
ada.blah()If you run the Node debugger, you can see the prototype chain by looking at the [[Prototype]] field of any object:z

Practice & Review
Study Questions
Then, with a partner, discuss these questions:
Last updated