DEV Community

Rudra Pratap
Rudra Pratap

Posted on

The Complete Guide to OOP In Javascript

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.
Prototypal Inheritance Demo

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.


Image description

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


Image description

  • 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:

  1. Constructor functions
  2. ES6 classes
  3. 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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode



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.


Image description

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.

Image description

Anonymous functions can't be made constructors.

Image description



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.


Enter fullscreen mode Exit fullscreen mode

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}.`)
}

Enter fullscreen mode Exit fullscreen mode

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.

Enter fullscreen mode Exit fullscreen mode



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.

Enter fullscreen mode Exit fullscreen mode

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
    ....
}

Enter fullscreen mode Exit fullscreen mode

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

  1. using constructor function

let Student = function (firstName,lastName){
    this.firstName = firstName
    this.lastName = lastName
}

Enter fullscreen mode Exit fullscreen mode
  1. using class

class Student {
    constructor(firstName,lastName){
        this.firstName = firstName
        this.lastName = lastName
    }
}

Enter fullscreen mode Exit fullscreen mode



Defining methods : constructor functions vs classes

  1. 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}.`)
}


Enter fullscreen mode Exit fullscreen mode
  1. In class


class Student {
    constructor(firstName,lastName){
        this.firstName = firstName
        this.lastName = lastName
    }

    introduceMyself(){
     console.log(`Hello, my name is ${this.firstName} ${this.lastName}.`)
    }

}


Enter fullscreen mode Exit fullscreen mode

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.

Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Puzzle
Find the output of the following snippet


var Employee = {
  company: 'xyz'
}
var emp1 = Object.create(Employee);
delete emp1.company
console.log(emp1.company);

Enter fullscreen mode Exit fullscreen mode

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) // {}

Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
fruntend profile image
fruntend

Сongratulations 🥳! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up 👍

Collapse
 
merudra754 profile image
Rudra Pratap

Thanks 😊