Keeping the momentum from my last article, this article aims to explain how sub-classes work in JavaScript.
Sub-classes are what make inheritance possible, which is one of the core aspects of the OOP paradigm. This allows us to maintain the behavior of one object in another without the need to do the same work again.
JavaScript inheritance can be a bit confusing because our subclass will not "inherit" the properties from its parent, it will just have access to the same fields, so even if inheritance is the standard term be careful to not be confused with this.
So how does sub-classing work? Let’s show with code:
function employeeCreator(name) {
const newEmployee = Object.create(employeeFunctions);
newEmployee.name = name;
return newEmployee;
}
const employeeFunctions = {
sayName: function () {
console.log(`My name is ${this.name}.`);
},
};
const employee = employeeCreator("Alice");
employee.sayName(); // My name is Alice.
function developerCreator(name, language) {
const newDeveloper = employeeCreator(name);
Object.setPrototypeOf(newDeveloper, developerFunctions);
newDeveloper.language = language;
return newDeveloper;
}
const developerFunctions = {
code: function () {
console.log(`${this.name} is coding with ${this.language}.`);
},
};
Object.setPrototypeOf(developerFunctions, employeeFunctions);
const developer = developerCreator("Bob", "JavaScript");
developer.sayName(); // My name is Bob.
developer.code(); // Bob is coding with JavaScript.
Here we want to maintain the employee properties in developer without duplication, so we created an inheritance manually using employeeCreator
as the parent class and developerCreator
as a sub-class.
We achieve this by creating a new employee with the employeeCreator
, then we extend this employee object with developer functionalities using the method setPrototypeOf
, then we can access any developer attributes from the employee object, and with the help of object.setPrototypeOf which let us have access to another object properties by getting it’s prototype
and with this we can get the sayName
into the developerFunctions
and all of this in the newDeveloper
.
This "manual" approach to inheritance in JavaScript, however, is not the recommended way since it’s not the most readable or performant way to do It. So let’s step up a bit by using the new
keyword.
function Employee(name) {
this.name = name;
}
Employee.prototype.sayName = function () {
console.log(`My name is ${this.name}.`);
};
const employee = new Employee("Alice");
employee.sayName(); // My name is Alice.
function Developer(name, language) {
Employee.call(this, name);
this.language = language;
}
Developer.prototype = Object.create(Employee.prototype);
Developer.prototype.code = function () {
console.log(`${this.name} is coding with ${this.language}.`);
};
const developer = new Developer("Bob", "JavaScript");
developer.sayName(); // My name is Bob.
developer.code(); // Bob is coding with JavaScript.
As you can see, some things changed. Now we can use the new
keyword to create a new object (instead of using Object.create
) and we also can call the constructor function Employee
to initialize its properties and add functions to EmployeeCreator.prototype
.
In Developer
we use call (instead of Object.setPrototypeOf
) to use the parent constructor on this new object we’re creating, and after that, we add a language
value to this
. To keep up with Employee
functionalities, we pass its prototype to Developer.prototype
by creating an object with it.
The downside of this approach is it is verbose and still not very readable. So the JavaScript team tried to fix this with the next solution:
class Employee {
constructor(name) {
this.name = name;
}
sayName() {
console.log(`My name is ${this.name}.`);
}
}
const employee = new Employee("Alice");
employee.sayName(); // My name is Alice.
class Developer extends Employee {
constructor(name, language) {
super(name);
this.language = language;
}
code() {
console.log(`${this.name} is coding with ${this.language}.`);
}
}
const developer = new Developer("Bob", "JavaScript");
developer.sayName(); // My name is Bob.
developer.code(); // Bob is coding with JavaScript.
Now we’re using the ES2015 approach, as I said in my class article the class solution is just syntactic sugar to the last solution and works in the same way, but it’s cleaner and easier to read if compared to the others.
For the Developer
the this
keyword is uninitialized, so we must call super
(which replaces the Object.call
) before we call this
. This happens because our object is only later instanced and returned in the Employee
to be automatically assigned to this
in the Developer
, so we can’t just call this
at this point, but only after the super
. It’s something like this = super(name)
.
But how does super
knows it needs to run Employee
? It’s just because of the extends
keyword, which replaces the need to add functions "behind the scenes", directly in the prototype
. Also, it sets a reference to the Employee
constructor in the Developer
__proto__
in a property on Objects
that points to the prototype
set in them.
And that’s the class
approach. This is what makes it possible to emulate an object-oriented language with a prototype-based environment.
Thanks for reading, if you want to improve your knowledge from here you can check the references for more.
References:
https://frontendmasters.com/courses/object-oriented-js
https://262.ecma-international.org/9.0/#sec-ordinary-and-exotic-objects-behaviours
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
https://www.youtube.com/watch?v=DqGwxR_0d1M
We want to work with you. Check out our "What We Do" section!