The Complete Guide to OOP In Javascript
Rudra Pratap
Posted on June 2, 2023
We know that Javascript is a multi-paradigm language. That is, it supports functional as well as object oriented programming.
In this blog, I am explaining the concept of OOP in Javascript in detail.
What is Object Oriented Programming
Object Oriented Programming is a programming paradigm based on the concept of objects.
Objects are used to model (describe) the real-world or abstract features.
Objects contain data (properties) and behaviour (methods). By using objects we can pack data and corresponding behaviour in one block.
OOP was developed with the goal of organizing code, to make more flexible and easier to maintain (avoid spaghetti code).
There are six very important terminologies related to OOP:
1] Classes
- Class is a blueprint or templates to build new objects.
- Classes are non-tangible logical entity, that is, they don't occupy memory.
- A class can exist without its object but converse is not true.
2] Objects
- Objects are instances of class.
- They are real world entities, ie they occupy memory.
3] Data Abstraction
- Data abstraction means hiding the unnecessary details and only showing the useful data to the user.
4] Data Encapsulation
- Encapsulation means wrapping up of properties and behaviours in a single unit.
- Encapsulation is achieved by making all the properties and some methods private in a class.
- Encapsulation leads to data hiding.
- Getters are used to accept values and setters are used to change the class state, hence restricting the outsiders to manipulate the class state.
- Encapsulation promotes loose coupling, ie, other sections of the code can be changed or refactored without affecting another parts.
- Encapsulation helps in maintaining security and avoiding accidental bugs, because the properties/fields are hidden from the outer world.
- In Javascript, encapsulation can be achieved by using the ES6 classes as well as function closures.
5] Polymorphism
- Polymorphism means the ability of a method to act in more the one way, depending upon the situation.
- A child class can override the methods present in its parent class.
- It promotes the DRY (Don't Repeat Yourself) principle.
6] Inheritance
- It means the ability of an object to inherit properties and behaviours from its parent object.
- Inheritance promotes reusability and maintainability.
Now we have learnt the important terminologies of OOP. Let us now learn how actually OOP is implemented in Javascript.
OOP in Javascript is completely different from other traditional object oriented programming languages like C++ and Java.
- Everything in Javascript is either a primitive or an object.
In Javascript, Object Oriented Programming is implemented using Prototypes and Prototypal Inheritence
Prototypes
Prototypes are the mechanism by which Javascript objects inherit features from one another.
Prototypal Inheritance
Before going to the definition of prototypal inheritance let me ask you a question.
You write the following code in console.
Here, you have made only one property in the Student object, but you can see in the following image that there are other properties and methods already available in the Student object along with the name property.
So how is that possible? It is possible because of prototypal inheritance.
Prototype inheritance in javascript is the linking of prototypes of a parent object to a child object to share and utilize the properties of a parent class using a child class.
Prototypes are hidden objects that are used to share the properties and methods of a parent class to child classes.
The prototype contains methods that are accessible to all objects linked to that prototype.
Classical OOP vs OOP in JS
- In classical OOP, the child class have all the public properties and methods of its parent class. That is, flow of data is from parent to child.
- In Javascript, the child object delegates or entrust the methods and properties to its prototype object. Thai is, flow of data is from child to parent.
How do we actually create prototypes? And how do we link objects to prototypes? How can we create new objects, without having classes?
Prototypes can be created using three ways:
- Constructor functions
- ES6 classes
- Object.create() method
Now, I will explain each way in detail :)
1. Constructor Functions
When a regular function is called using the new keyword, then the function acts as a constructor fuction and returns a brand new object, whose properties and methods can be set by passing parameters and attaching those values to the this keyword.
let Student = function (firstName,lastName){
this.firstName = firstName
this.lastName = lastName
}
let rudra = new Student("Rudra","Pratap")
console.log(rudra) // Student { firstName: 'Rudra', lastName: 'Pratap' }
console.log(rudra.firstName) // Rudra
Now, we can use this Student constructor function to declare as many other objects.
let rudra = new Student("Rudra","Pratap")
console.log(rudra.firstName) // Rudra
let tim = new Student("Tim","Cook")
console.log(tim.lastName) // Cook
let zuck = new Student("Mark","Zuck")
console.log(zuck.firstName) // Zuck
Every constructor function has a property called prototype. And that prototype property contains at least two more properties : the definition of the constructor function and a __proto__ object.
Every object of a constructor function have access to the properties and methods present in the prototype of its parent constructor function.
Note: Anonymous functions can't be made constructor functions.
Regular functions can be made constructors.
Anonymous functions can't be made constructors.
Let suppose you want a method introduceMyself(), that can be called on each object, how can we do it?
First solution [Naive]
let Student = function (firstName,lastName){
this.firstName = firstName
this.lastName = lastName
this.introduceMyself = function(){
console.log(`Hello, my name is ${this.firstName} ${this.lastName}.`)
}
}
let rudra = new Student("Rudra","Pratap")
rudra.introduceMyself()
let tim = new Student("Tim","Cook") // Hello, my name is Rudra Pratap.
tim.introduceMyself() // Hello, my name is Tim Cook.
This will work as we wanted, but it is very inefficient. Suppose you are working on a large project having thousands of objects constructed by the Student constructor function, each of the object will have this introduceMyself() methods, as a result a large amount of memory will be wasted just to store a method which is common to all objects.
Second Solution [Optimized]
A efficient solution would be adding the introduceMyself() method in the constructor function's prototype.
let Student = function (firstName,lastName){
this.firstName = firstName
this.lastName = lastName
}
Student.prototype.introduceMyself = function(){
console.log(`Hello, my name is ${this.firstName} ${this.lastName}.`)
}
As we already know that the object has access to the prototype of its parent constructor function through the __proto__ object.
Whenever we try to access a method or a property in an object by using a dot or bracket notation, that method/property is first searched inside the object, if it is found there then it is used, and if it is not found there, the Javascript engine will look into its parent object (here constructor function) prototype and tries to find out that method/property.
let rp = new Student("Rudra","Pratap")
rp.introduceMyself() // Hello, my name is Rudra Pratap.
Hence, we can make many objects of a constructor function and we have to only define the methods once, inside its prototype. All its child object will automatically inherit those methods.
let Student = function (firstName,lastName){
this.firstName = firstName
this.lastName = lastName
}
Student.prototype.introduceMyself = function(){
console.log(`Hello, my name is ${this.firstName} ${this.lastName}.`)
}
let rp = new Student("Rudra","Pratap")
rp.introduceMyself() // Hello, my name is Rudra Pratap.
let tim = new Student("Tim","Cook")
tim.introduceMyself() // Hello, my name is Tim Cook.
let zuck = new Student("Mark","Zuck")
zuck.introduceMyself() // Hello, my name is Mark Zuck.
2. ES6 Classes
- ES6 classes are just syntactic sugar over constructor functions.
- They are not like the classes found in traditional OOP languages like Java and C++.
- They are just the transformed form of constructor functions, under the hood they use the concept of prototypes and prototypal inheritance.
- Classes were added in Javascript in 2015 so that programmers from other backgrounds like Java and C++, can write object oriented code more easily.
- Using classes, we don’t have to manually mess with the prototype property.
Classes can be defined by two types: class declaration and class expression.
let Person = class { ... } // class expression
class Person{ // class declaration
....
}
A class must have a constructor method.
Constructor Methods
- The constructor method is a special method of a class for creating and initializing an object instance of that class.
- A class can't have more than one constructor method.
- Classes are not hoisted.
Class Declaration : constructor functions vs classes
- using constructor function
let Student = function (firstName,lastName){
this.firstName = firstName
this.lastName = lastName
}
- using class
class Student {
constructor(firstName,lastName){
this.firstName = firstName
this.lastName = lastName
}
}
Defining methods : constructor functions vs classes
- In constructor function
let Student = function (firstName,lastName){
this.firstName = firstName
this.lastName = lastName
}
Student.prototype.introduceMyself = function(){
console.log(`Hello, my name is ${this.firstName} ${this.lastName}.`)
}
- In class
class Student {
constructor(firstName,lastName){
this.firstName = firstName
this.lastName = lastName
}
introduceMyself(){
console.log(`Hello, my name is ${this.firstName} ${this.lastName}.`)
}
}
Now we know that there is slight difference in syntax of constructor function and classes, but the way to instantiate them is exactly the same for each of them.
let rp = new Student("Rudra","Pratap")
rp.introduceMyself() // Hello, my name is Rudra Pratap.
2. Object.create()
As I have already wrote that OOP in Javascript can be achieved by Constructor Functions, ES6 classes and Object.create(), it's time to discuss the last one.
Object.create() is used to manually set the prototype of an object to any other object that we want.
let Person = {
greet:function(){
console.log(`Hi ${this.name}`)
}
}
let rp = Object.create(Person)
rp.name="Rudra"
rp.greet() // Hi Rudra
console.log(rp.__proto__ === Person) // true
console.log(rp.__proto__.__proto__ === Object.prototype) // true
console.log(rp.__proto__.__proto__.__proto__) // null
let Person = {
greet:function(){
console.log(`Hi ${this.name}`)
}
}
let Teacher = Object.create(Person)
Teacher.task = function(){
console.log("I am teaching")
}
let MathTeacher = Object.create(Teacher)
MathTeacher.name = "Rudra"
MathTeacher.greet() // Hi Rudra
MathTeacher.task() // I am teaching
console.log( MathTeacher.__proto__ === Teacher ) // true
console.log( MathTeacher.__proto__.__proto__ === Person ) // true
console.log( MathTeacher.__proto__.__proto__.__proto__ === Object.prototype ) // true
console.log( MathTeacher.__proto__.__proto__.__proto__.__proto__ ) // null
Object.create() is the least common way to implement prototypal inheritance, but if it is need then we can do more programmatically, like
let Person = {
init:function(firstName,lastName){
this.firstName = firstName
this.lastName = lastName
},
greet: function(){
console.log(`Hi ${this.firstName} ${this.lastName}`)
}
}
let rudra = Object.create(Person)
rudra.init("Rudra","Pratap") // it is not a constructor
rudra.greet() // Hi Rudra Pratap
Puzzle
Find the output of the following snippet
var Employee = {
company: 'xyz'
}
var emp1 = Object.create(Employee);
delete emp1.company
console.log(emp1.company);
Solution:
The answer is ‘xyz’, because there is no property company in emp1 object, but in its prototype which is Employee.
If you want to delete the property company, then
var Employee = {
company: 'xyz'
}
var emp1 = Object.create(Employee);
delete emp1.__proto__.company
console.log(emp1.company) // undefined
console.log(Employee) // {}
Posted on June 2, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.