Most developers don’t know that the beloved scripting language also has secured OOP paradigm capabilities under its belt. These are features that JavaScript developers should know, but they don’t.
Irrespective of how JS implements these features, they work like a scripting language with strange quirks behind the scenes.
JavaScript never ceases to amaze me, and I don’t want to keep all this to myself. Let’s begin by understanding the concept of Inheritance and JS’ approach towards it.
Inheritance
The Inheritance implementation in OOP-based languages, like Java, differs from JavaScript. Those languages were programming languages since their inception.
However, JavaScript stems from a different pair of roots. It implements all these features in a strange ritual and has various operations in the background to make those features possible.
JS requires workarounds to implement such features because Brendan Eich did not want it to operate like an OOP-based language. However, the concept of Inheritance for Java and JavaScript remains the same.
The primary Inheritance idea is to pass down properties and methods of a class to another new child class or object without creating duplicate values.
Why use Inheritance?
Assume there is Class A and Class B. If class A has a property that class B requires, then Class B can create that property again. But that would be repetitive and a violation of the DRY principle.
Software engineering is all about reusability and readability. If developers create duplicate variables, fellow engineers and themselves will face problems understanding the logic and where those variables reside.
That’s why we use Inheritance. It allows child classes and objects to inherit properties, values, and methods from parent classes and objects that have them already to avoid writing the same logic repeatedly.
For OOP-based languages, Inheritance is a way to securely grab values from one class to another, as each class represents a component or feature in the application.
Similarly, when your grandparents or parents are wealthy, your requirements to work hard are significantly lower because top-level members satisfy those requirements already.
When parents are wealthy, some children continue their legacy while others inherit and enjoy their lives. It is optional to have more properties, not the real estate ones, but the ones in objects, after inheriting some of them, which is the same in programming.
Child classes serve more purpose even after inheriting properties and methods from parent classes.
How is Inheritance implemented in JS?
JavaScript is not an object-oriented language. Inheritance is derived from languages like Java, C++, and others because they require Classes to write any code.
They preach the idea of secure code. To write a print statement in Java, you write them inside Classes, so the requirement of Inheritance there becomes viable.
Typically, developers use classes with constructors to implement Inheritance in those languages with zero object requirements.
However, JavaScript desires to stay unique. It uses objects to implement Inheritance. That’s standard for JavaScript developers, but OOP-based developers perceive it as a nightmare.
Trust me, objects are a part of a massive syntactical sugar play. JavaScript implements it using a mechanism called Prototype alongside a few constructor functions.
First, I will explain Inheritance with Classes in JavaScript since most developers are familiar with it concerning classes in languages like Java. I will also explain the purpose of Classes for a bit more background.
What is class-based Inheritance?
The coding world would look difficult without a class for OOP developers. Anyone would hate writing duplicate code. For instance, look at the following example.
"use strict";
// Model 1 of Porsche
const car1 = {
brand: "Porsche",
model: "911 GT3",
topSpeed: "318 KM/h",
acceleration: "3.4",
carDetails: function () {
console.log(
`This is a ${this.model} by ${this.brand} with a top speed of
${this.topSpeed} and accelerates from 0 to 100 KM/h in
${this.acceleration} seconds`
);
},
};
// Model 2 of Porsche
const car2 = {
brand: "Porsche",
model: "718 Cayman GT4 RS",
topSpeed: "315 KM/h",
acceleration: "3.4",
carDetails: function () {
console.log(
`This is a ${this.model} by ${this.brand} with a top speed of
${this.topSpeed} and accelerates from 0 to 100 KM/h in
${this.acceleration} seconds`
);
},
};
car1.carDetails();
car2.carDetails();
I created two objects for two cars from the same brand - Porsche. You can notice the repetitive properties and methods between both objects.
The brand
name, acceleration
property, and the carDetails()
method are the same in both objects. There's no difference in their values or syntax. It is called Code Duplication.
The property values, such as acceleration
, might change later, but the carDetails()
method will display the same properties and remain unmodified. Why should we duplicate and keep it in two places if we cannot modify the method? That is why the world introduced Classes.
Classes allow us to write the template and boilerplate for such scenarios. We can insert the names of properties and write functions, like carDetails()
, inside them using those properties.
Based on the details from these classes, developers create objects, which are instances of Classes.
Objects copy the barebone details from Classes and modify them to their needs and uniqueness. Not only duplication, but if you wish to alter the carDetails()
method, there is no unified approach.
Only one method inside one object will get modified at a time.
I am sure you do not dream of sitting and implementing the same modifications to each similar object on a chill weekend. Hence, let me turn the above code into a version that uses a class. I'm sure you are starting to see the purpose of Inheritance.
class porscheCar {
// Constructor
constructor(carModel, carTopSpeed, accl) {
this.model = carModel;
this.topSpeed = carTopSpeed;
this.acceleration = accl;
}
// Default Brand Name
brand = "Porsche";
// Display Function
carDetails() {
const result = `This is a ${this.model} by ${this.brand} with
a top speed of ${this.topSpeed} and accelerates from 0 to 100
KM/h in ${this.acceleration} seconds`;
return result;
}
}
// Creating new Objects of porscheCar Class
const car1 = new porscheCar("911 GT3", "318 KM/h", "3.4");
const car2 = new porscheCar("911 GT3", "318 KM/h", "3.4");
console.log(car1.carDetails());
console.log(car2.carDetails());
We had two problems - Code Duplication and the need to modify multiple objects and their methods by hand. We solved the first problem, but we will need to fix the second problem.
We cannot modify the methods of the objects belonging to a class directly. We cannot do car1 = function() {}
because it would only update car1
and not car2
together. That would require us to modify the class.
However, before we tackle the second problem, I want to talk about extending classes. We're diving deeper into the Prototype mechanism to modify the objects together through code and not manual modifications.
But what if I had another class called mercedesCar
and wanted the same properties and methods with other unique values? As I explained earlier, we have to avoid code duplication.
Hence, we will use the extends
keyword in JavaScript that passes down the properties and methods of porscheCar
to mercedesCar
and perform Inheritance. Remember that we still need to solve the second modification problem, but I will tackle this first because I prefer Benz.
Extending Classes
Traditionally, this is how Inheritance would take place in other languages using the extends keyword.
class mercedesCar extends porscheCar {
constructor(carModel, carTopSpeed, accl) {
super();
this.model = carModel;
this.topSpeed = carTopSpeed;
this.acceleration = accl;
this.brand = "Mercedes";
}
}
const mb1 = new mercedesCar("300 SLR", "290 km/h", "7.7", "1955");
console.log(mb1.carDetails());
The constructor takes the information from the parameters while creating the object and delivers it to the extended parent porscheCar
class.
Constructors inside classes initialize an object and assign unique values for the boilerplate inside classes. The super()
method invokes the constructor of the extended porscheCar
class and is necessary because we try to access the variables of porscheCar
inside mercedesCar
.
If we do not include super()
, we cannot use the this
keyword to access and assign new variables to the porscheCar
class.
We need to access and assign values to the porscheCar
class because the carDetails()
method belongs to the porscheCar
class.
The carDetails() method requires the model name, top speed, brand name, and acceleration data of the legendary 300 SLR to print to the console.
Therefore, we used super()
and provided the value for its properties using the constructor. I created an instance of the mercedesCar
class and invoked the carDetails()
method even though it belonged to the porscheCar Car class because I had called super()
to deliver the values of Mercedes to Porsche 🏎️.
Prototype
JavaScript is different. It doesn’t like the standard behaviour of the class
and extends
keywords. It does something else under the hood even though it uses the class
keyword.
In the example above, I inherited the methods and properties of one class to another, which is the traditional approach. But now, let us revert to the second problem we had earlier.
We had two objects called car1
and car2
separately and then transferred to the porscheCar
class to solve the first problem of code duplication.
After creating the class and initiating those objects, I wanted to modify the statements inside the carDetails()
method. However, I cannot assign a new function because I use a class to wrap those objects and methods. They are secure and cannot be modified directly.
car1.carDetails = function() {}...
It will not work. The car2
object will still have the old carDetails()
method even if, theoretically, someone modifies the carDetails()
method inside the car1
object.
In other languages, you can modify the methods of an object independently because those languages create a copy of everything inside a class and give that to its objects. We can do that in JS but with its disadvantages.
We cannot modify the method of any object related to a class because those methods do not belong to the objects in the first place. In Java, after building the same foundation for both objects using the boilerplate code in a class, I could if I wanted the method in car1
to stay different from car2
.
We cannot do that in JS because it doesn’t copy the contents of a class and give it to the object. Instead, JavaScript lends those methods inside the class to those objects through a reference. In reality, those methods still reside inside the class and are given for rent to its objects while they want.
It is called the prototype mechanism. Each object has an interlinked copy object called its prototype. Each object has its prototype, and other objects cannot modify it.
A prototype of an object or value is a copy of the properties and methods present inside the object.
In the same way, a Class has a prototype of its own. This prototype gets shared with each object inside it, and those objects can use that to invoke methods and play with its properties but not modify it.
I must modify the prototype of the porscheClass
class if I want to solve our second problem of changing the methods of a Class. These changes should appear for each object.
porscheCar.prototype.carDetails = function () {
const result = `You are looking at ${this.brand}'s
${this.model} with a top speed of ${this.topSpeed} and
swifts from 1 to 100 KM/h in ${this.topSpeed} seconds.`;
return result;
};
console.log(car1.carDetails());
console.log(car2.carDetails());
The code for the class and the creation of car1
and car2
remain the same. I modified the string inside the carDetails()
method using the prototype property binded to the class.
That property allows us to access the properties and methods of the respective class and alter them outside the class. It isn’t secure at all, and I know the OOP folks are shouting at me virtually through the screen, but as I said, JavaScript is not for this.
The class
keyword is simply sugarcoating the concept of OOP. Developers requested that keyword. On the face of it, they gave the feature. But under the hood, the Prototype mechanism initiates the objects and shares the properties and methods of the classes with its properties instead of giving them a copy.
Objects initiated using Classes can have unique properties when their properties inside the class initialize using the this
keyword because that keyword refers to each object individually and not as a whole.
Therefore, I will get the properties of car1
returned as an object because they are uniquely defined. However, the carDetails()
method will remain in the prototype because that method is typical for each object, including car2
.
The [[Prototype]]
represents the Prototype object and belongs to the porscheCar
class. The square brackets represent an object that cannot be modified.
It is how JavaScript represents Prototypes objects, but there’s also __proto__
. However, it is deprecated and controversial, so I will let it stay in the shadows.
Constructors and Classes
Try to observe the following code. Maybe there’s not more than an empty object to observe. And still, the output has a Prototype with selective methods even though the object is empty.
console.log({});
The JavaScript language has a set of default methods for each object in the language to access and use. You can use them even if they are not explicitly defined. As I explained earlier, each object connects to a copy of itself and creates a Prototype chain.
Each empty object points to an unnamed object, and your objects inherit properties and methods from that nameless object. It happens because each object in JavaScript is an instance of a class named "Object" with a capital "O".
Each object in JavaScript inherits the Object constructor information or class and displays that irrespective of whether the object has anything inside it.
You will find the default properties of the Object class because car1
is an object after all if you try to access the Prototype of the car1
object Prototype. It does not work with Objects only, and the same principles apply to Arrays.
And finally, the object-based inheritance.
Object-based Inheritance
In JavaScript, there are a few ways one can initiate an object. We have been using the first and most preferable approach throughout this article using the {}
object literal. However, we can also create them using a function. Take the example below.
function createBenz(modelName, brandName, engine, releaseYear) {
this.model = modelName;
this.brand = brandName;
this.engine = engine;
function returnDetails() {
console.log(
`You are looking at ${this.brand}'s ${this.model} with a
${engine} engine`
);
}
}
const mb2 = new createBenz("S-Class S680", "Mercedes Maybach", "V12");
console.log(mb2);
It only happens using the function
keyword using the Function Declaration approach. The arrow functions and function expressions cannot become a part-time object constructor.
Furthermore, I will try to access the prototype of this object that I initiated through a constructor function.
const mb3 = new createBenz(
"S-Class S680",
"Mercedes Maybach",
"V12"
);
createBenz.prototype.returnDetails = function () {
console.log(
`You are looking at ${this.brand}'s ${this.model} with a
${this.engine} engine`
);
};
createBenz.prototype.greetCustomer = function () {
return `Welcome to Mercedes Benz 👋`;
};
console.log(mb3);
The object prototype now holds the new greetCustomer()
and returnDetails()
method. It is how we can use Inheritance with objects. It marks one of the possibilities. I created another instance of createBenz()
without a class and inherited its methods and properties into an m3
object.
const mb4 = new createBenz(
"C-Class C250",
"Mercedes Benz",
"2.2-liter BlueEfficiency"
);
createBenz.prototype.returnDetails = function () {
console.log(
`You are looking at ${this.brand}'s ${this.model} with a
${this.engine} engine`
);
};
createBenz.prototype.greetCustomer = function () {
return `Welcome to Mercedes Benz 👋`;
};
console.log(mb3.returnDetails());
console.log(mb3.greetCustomer());
Prototypal Inheritance
As I said before, objects are used alongside prototypes in JavaScript to achieve Inheritance. That is correct, and is called Prototypal Inheritance.
Except for the extending class section, I demonstrated the examples of Prototypal Inheritance with the porscheCar
and mercedesCar
classes without telling you because, under the hood, JavaScript uses Prototypes, constructor functions as shown in the object-based inheritance sections, and objects themselves to perform Inheritance.
It is by default Prototypal Inheritance even though it may appear as standard Inheritance using classes, but the reality hides behind objects.
The concept of Classes in our context is sugarcoated. Those keywords from other languages allow us to add an abstraction layer on top of the actual functionalities of JavaScript.
Those keywords, like class
and constructor
, are easier to read and write prototypes and Inheritance with objects. The syntax of Prototypes and objects is strange and baffling to understand.
Hence, JS has a layer that covers Prototypal Inheritance and other concepts with keywords that seem relatable and relevant from other languages. They perform almost the same functionalities. However, they are different in JavaScript because of its design.
The syntax to write Classes came in ES6 even though prototypes and objects controlled its functionalities. The keywords performed their job like in other languages to a standard developer.
Under the hood
When you create the porscheCar
class, JavaScript creates a constructor function beneath the syntactic sugar. It initializes the desired properties and methods within the class prototype after building the porscheCar
class.
// Creates the constructor function
function porscheCar() {}
// Creates the methods inside the Prototype
porscheCar.prototype.displayBrand = function () {
console.log(`Brand: ${this.brand}`);
};
// Creates an Instance of the constructor
const car1 = new porscheCar();
// Creates the unique property for each object
car1.brand = "Porsche";
// Invokes the method through the Prototype
car1.displayBrand();
The output is — Brand: Porsche. The object-based Inheritance had a similar example. I used a standard function as a constructor function when I attached it with the new
keyword.
I created a new method inside the prototype of the constructor function. Each object will inherit this prototype with its method, such as displayBrand()
.
JavaScript creates properties with unique values for each object once an instance or object gets created. These are not a part of the prototype because I used the this
keyword, and that keyword always points to the owner object and not a class.
The properties are for the object, so they remain unique. Further, they derive it from the constructor function Prototype when developers invoke its methods.
Inheriting Properties VS Methods
Allow me to explain this further. As I stated earlier, the properties and methods belong exclusively to the object because that is how the this
keyword works instead of focusing on the constructor function or the class.
We directly attached the displayBrand()
method to the constructor function prototype. However, you can eliminate the default prototype behaviour by using the this
keyword while making the methods inside the class or constructor functions if you want these methods to stay exclusively unique for each object.
I hate using constructor functions. Let me show the difference using a standard class.
class atomicHabits {
author = "James Clear";
bookName = "Atomic Habits";
constructor(rating, comments) {
this.ratingOfBook = rating;
this.extraComments = comments;
this.displayProperties = function () {
return `The ${this.bookName} book by ${this.author} has a rating of
${this.ratingOfBook} and "${this.extraComments}" extra comment(s).`;
};
}
}
const reader1 = new atomicHabits("4.8", "N/A");
const reader2 = new atomicHabits("4.95",
"One of the best productivity books!");
console.log(atomicHabits);
console.log(reader1);
console.log(reader2);
console.log(reader1.displayProperties());
console.log(reader2.displayProperties());
The author
and bookName
properties are outside the constructor because I don’t need input from the user about the book author and name.
I have to attach the displayProperties()
method to each object without using the atomicHabit class prototype to make it unique for each object.
We require a constructor, method, or accessor to use the this
keyword inside a class. I will avoid explaining getters and setters right now. Since I cannot use the this
keyword outside the constructor, I defined the displayProperties()
method using the this
keyword inside the constructor itself.
Now, the method will convert into a property and get attached to each. And this is precisely the difference between properties and methods. You target each object individually using the this
keyword. Otherwise, you can define a function as a standard method each object inherits. You cannot modify it when the method is not an explicit property of the object.
The author and bookName properties are not a part of the prototype but are unique to each object even without the this
keyword attached to them. I didn’t use the this
keyword because I am not creating and initializing it with a new value.
However, the displayProperties()
function is now a property instead of a method. You can modify it for each object manually to perform different operations for various objects, and the properties remain changeable.
The displayProperties()
method is now a part of each object, and you can manually change it for one of the objects. Instead of inheriting the methods, these objects inherit them as properties. You can modify them now.
It can also be a disadvantage. If you want to modify the method in multiple objects, you must alter them manually. It brings the second problem back. Hence, we only use them in specific scenarios and prefer prototypes in others.
reader1.displayProperties = function () {
return `Changed without prototype! This will not change reader2 object!`;
};
console.log(reader1.displayProperties());
console.log(reader2.displayProperties());
I changed the method for the reader1
method without modifying the prototype and allowing the reader2
object to remain stable.
The syntax and implementation seem like Java now. It means I should start packing my bags. See you, lads.
Top comments (0)