JavaScript Subclasses
March 22, 2020
Let’s learn how to create a subclass using Prototypal Inheritance
Let’s write out the Prototypal chain for Person
-
myPerson —> Person.prototype —> Object.prototype —> null
- null marks the end of the Prototypal chain
- The nice thing is the Person gets behavior from the Object
We created a class of Person
person.js
class Person {
constructor(firstName, lastName, age, likes = []) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.likes = likes;
}
getBio() {
let bio = `${this.firstName} is ${this.age}.`;
this.likes.forEach(like => {
bio += ` ${this.firstName} likes ${like}.`;
});
return bio;
}
setName(fullName) {
const names = fullName.split(' ');
this.firstName = names[0];
this.lastName = names[1];
}
}
But what if we created a different type of person
- An employee
- A teacher
- A student
- A developer
- A designer
- All of the above are different types of “Person”
-
They all share similarities but they also have subtle differences
- A developer might understand SQL but a designer does not
One way to copy a Person
- Just right another class for each
- But that has one glaring problem… LOT’S OF DUPLICATE code
- We want to code DRY (Don’t Repeat Yourself)
A better way
- Create a class that inherits behavior from Person
We’ll create a subclass of Person called Employee
- We can inherit behavior from a class using
extends
Prove we just created a copy of Person
- It really isn’t that useful right now but let’s prove that Person and Employee have same functionality
class Person {
constructor(firstName, lastName, age, likes = []) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.likes = likes;
}
getBio() {
let bio = `${this.firstName} is ${this.age}.`;
this.likes.forEach(like => {
bio += ` ${this.firstName} likes ${like}.`;
});
return bio;
}
setName(fullName) {
const names = fullName.split(' ');
this.firstName = names[0];
this.lastName = names[1];
}
}
class Employee extends Person {
//
}
// create a subclass of Person call Employee
const myPerson = new Employee('John', 'Doe', 33, ['Developer', 'Soccer']);
console.log(myPerson.getBio());
console.log(myPerson.setName('Jane Doe'));
console.log(myPerson.getBio());
- You’ll see it works just like Person
Now we can override methods or provide brand new methods on our subclass
- If we don’t change anything we get the same functionality
-
If we want to override the constructor we add new arguments
- The arguments can be in any order you wish
class Employee extends Person {
constructor(firstName, lastName, age, position, likes) {}
}
// create an instance of PersonClass
const myPerson = new Employee('John', 'Doe', 33, 'IT', ['Developer', 'Soccer']);
console.log(myPerson);
// MORE CODE
Houston we have a problem!
- We are getting an error:
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
What does this error mean?
-
It means that inside of our subclass constructor functions we have to call the parent constructor function and we do that using
super()
- This will make sure the firstName, lastName, age and likes get set up correctly
- All we have to do is this:
// MORE CODE
class Employee extends Person {
constructor(firstName, lastName, age, position, likes) {
super(); // add super here
}
}
// MORE CODE
-
important super() gets called inside the subclass constructor
- But we also have to call it with the arguments the parent class expects
class Employee extends Person {
constructor(firstName, lastName, age, position, likes) {
super(firstName, lastName, age, likes);
}
}
Run
$ node person.js
- Output
Employee {
firstName: 'John',
lastName: 'Doe',
age: 33,
likes: [ 'Developer', 'Soccer' ]
}
- note If you don’t pass the arguments to
super()
if you run the app you won’t see the values populated in the terminal
Now we put the code in our subclass that makes our subclass (Employee) unique
class Employee extends Person {
constructor(firstName, lastName, age, position, likes) {
super(firstName, lastName, age, likes);
this.position = position;
}
}
- Will give you our new property
position
and it’s value ‘IT’ - Output:
Employee {
firstName: 'John',
lastName: 'Doe',
age: 33,
likes: [ 'Developer', 'Soccer' ],
position: 'IT'
}
Create an instance of Employee subclass
class Person {
constructor(firstName, lastName, age, likes = []) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.likes = likes;
}
getBio() {
let bio = `${this.firstName} is ${this.age}.`;
this.likes.forEach(like => {
bio += ` ${this.firstName} likes ${like}.`;
});
return bio;
}
setName(fullName) {
const names = fullName.split(' ');
this.firstName = names[0];
this.lastName = names[1];
}
}
class Employee extends Person {
constructor(firstName, lastName, age, position, likes) {
super(firstName, lastName, age, likes);
this.position = position;
}
}
// create an instance of PersonClass
const myPerson = new Employee('John', 'Doe', 33, 'IT', ['Developer', 'Soccer']);
console.log(myPerson);
- important The arguments we pass
new
Employee
// MORE CODE
// create an instance of subclass Employee
const myPerson = new Employee('John', 'Doe', 33, 'IT', ['Developer', 'Soccer']);
// MORE CODE
must match up with the values inside our subclass constructor
// MORE CODE
class Employee extends Person {
constructor(firstName, lastName, age, position, likes) {
// MORE CODE
-
Output
- We see we get position added to our subclass
Employee {
firstName: 'John',
lastName: 'Doe',
age: 33,
likes: [ 'Developer', 'Soccer' ],
position: 'IT'
}
What happens in the below code fragment?
// MORE CODE
const myPerson = new Employee('John', 'Doe', 33, 'IT', ['Developer', 'Soccer']);
myPerson.setName('Jane Doe');
console.log(myPerson.getBio());
// MORE CODE
- When I call setName it will look in Employee for
setName
and when it does not find it it will go to the parent Person class forsetName
Override methods in a subclass
- What if I want to override getBio in the Person class inside the Employee subclass?
- Maybe you want to replace the likes in getBio
likes
with the position of the employee at the company
// MORE CODE
class Employee extends Person {
constructor(firstName, lastName, age, position, company, likes) {
super(firstName, lastName, age, likes);
this.position = position;
this.company = company;
}
getBio() {
return `${this.firstName} ${this.lastName} in the ${this.position} department at ${this.company}`;
}
}
// create an instance of PersonClass
const myPerson = new Employee('John', 'Doe', 33, 'IT', 'IBM', [
'Developer',
'Soccer',
]);
myPerson.setName('Jane Doe');
console.log(myPerson.getBio());
// MORE CODE
-
Output will be:
Jane Doe in the IT department at IBM
- We are not changing the functionality of getBio in Person class but just overriding the message for the Employee subclass
- This will have no effect on “regular” Person people as we are not overriding anything specific to Person
- We are just saying if you are an Employee don’t use the Person getBio() use the Employee getBio() instead
Show both class and subclass with overriding method
// MORE CODE
// create an instance of PersonClass
const myPerson = new Employee('John', 'Doe', 33, 'IT', 'IBM', [
'Developer',
'Soccer',
]);
myPerson.setName('Jane Doe');
console.log(myPerson.getBio());
const myPerson2 = new Person('John', 'Doe', 33, ['Developer', 'Soccer']);
myPerson2.setName('Pip');
console.log(myPerson2.getBio());
// MORE CODE
Show the output
- And you’ll see the output showing it shows Employee with overriding method but the
Person2
just uses thegetBio()
from Person class
Jane Doe in the IT department at IBM
Pip is 33. Pip likes Developer. Pip likes Soccer.
Let’s add something completely unique to our subclass
- Let’s create a
getYearsLeft()
method just for our Employee subclass - getYearsLeft() will take the average retirement age (let’s use 65) and subtract the Employees age
// MORE CODE
class Employee extends Person {
constructor(firstName, lastName, age, position, company, likes) {
super(firstName, lastName, age, likes);
this.position = position;
this.company = company;
}
getBio() {
return `${this.firstName} ${this.lastName} in the ${this.position} department at ${this.company}`;
}
getYearsLeft() {
return 65 - this.age;
}
}
// create an instance of PersonClass
const myPerson = new Employee('John', 'Doe', 33, 'IT', 'IBM', [
'Developer',
'Soccer',
]);
myPerson.setName('Jane Doe');
// console.log(myPerson.getBio());
console.log(myPerson.getYearsLeft());
const myPerson2 = new Person('John', 'Doe', 33, ['Developer', 'Soccer']);
myPerson2.setName('Pip');
// console.log(myPerson2.getBio());
console.log(myPerson2.getYearsLeft());
- You will in our output we get
32
(65 - 33) formyPerson
- And we will get an error
TypeError: myPerson2.getYearsLeft is not a function
becausegetYearsLeft()
method doesn’t exist in the Person parent class
Challenge
- Create a subclass of Person for Student
- Track student grade (0 - 100)
- Override bio to print a passing or failing message
- 70 and above say “John is passing”
- Create “updatedGrade” method that takes the amount to add or remove from the grade
Test it out
- Create student
- Print status (failing or passing)
- Change grade to change status
- Print status again
Challenge Solution
class Student extends Person {
constructor(firstName, lastName, age, grade, likes) {
super(firstName, lastName, age, likes);
this.grade = grade;
}
checkPassing(grade) {
if (grade > 70) {
return 'passing';
} else {
return 'failing';
}
}
getBio() {
return `${this.firstName} is ${this.checkPassing(this.grade)}`;
}
updatedGrade(gradePoints) {
return (this.grade = this.grade + gradePoints);
}
}
const myStudent = new Student('John', 'Adams', 21, 70, ['Chess']);
console.log(myStudent.getBio());
myStudent.updatedGrade(1);
console.log(myStudent.getBio());
- Output
John is failing
John is passing