OOP in JavaScript: Encapsulation, Inheritance, Polymorphism, Abstraction, and Association

Aqib Ilyas
CodeX
Published in
6 min readJul 22, 2022

Object Oriented Programming is a commonly used software design pattern and is a very popular paradigm of programming. It’s used to structure code in such a way that code is reusable, easily readable, and scalable. It involves the use of classes and objects and almost all programming languages support this pattern.

In JavaScript, functions are objects. So we can achieve OOP pattern without the use of classes.

Let’s learn the basics of OOP and implement it using JavaScript.

Classes and Objects:

A class is a blueprint with which objects/variables are created. It is a custom data type containing properties and methods. An object is an instance of a class that uses the class’s methods and properties. Let’s understand and implement this via javascript:

class Animal{
constructor(name){
this.name = name;
}
}
// Let's create Object from the class Animal
var animal = new Animal("Dog");

A constructor is a special keyword reserved to create a constructor function. A constructor function is always called automatically upon the creation of an object/instance of a class. It implicitly returns an instance of the newly created object. A default constructor is built-in to the class and we override it by implementing our own. The new keyword is necessary to use with object creation as it will tell the compiler to invoke the constructor function.

In Javascript, we can create Objects using functions without the use of the class keyword.

function Animal(name){
this.name = name;
}
var animal = new Animal("Dog");

This is how we create Objects from functions and classes in javascript. Now that we know classes and objects, let’s learn some more OOP concepts.

Encapsulation:

Encapsulation is the process of hiding and securing the properties of objects. Direct access to encapsulated properties is not possible and we have to provide other mechanisms to operate/read that data. Typically, this is done by making class variable properties private and providing public class methods to access necessary data.

function Animal(val){
var name = val;
var publicApi = {
setName: function (val){
name = val;
},
getName: function (){
return name;
}
}

return publicApi;
}
var animal = new Animal("DOG");
animal.setName("CAT");
console.log(animal.getName()); output: CAT
console.log(animal.name); output: undefined

In the above code, the name property of Animal isn’t directly accessible but only via publicApi methods. This is because we are returning publicApi object that doesn’t have a name property. Its properties(setName and getName) utilize name property from its lexical scope.

Via class, this can be done by making class variables private using # before the name of the variable.

class Animal{
#name;
constructor(name){
this.#name = name;
}

setName(val){
this.#name = val;
}

getName(){
return this.#name;
}
}
var animal = new Animal("DOG");
animal.setName("CAT");
console.log(animal.getName()); output: CAT
console.log(animal.name); output: undefined

Encapsulation can also be done by making modules out of code

Abstraction:

Abstraction means hiding the implementation details and showing only behavior. It’s done on the design level as opposed to encapsulation which is implemented at the application level. With abstraction, only essential details are shown to the user.

Consider this code snippet:

class Employee{
#name;
#baseSalary;


setName(val){
this.#name = val;
}
setBaseSalary(val){
this.#baseSalary = val;
}

getName(){
return this.#name;
}

getSalary(){
let bonus = 1000;
return this.#baseSalary + bonus;
}
}
var emp = new Employee();
emp.setName("abc");
emp.setBaseSalary(100);
console.log(emp.getName());
console.log(emp.getSalary());

Notice how getSalary method is updating salary in the backend that the end user wouldn’t know. In a real application, this bonus can be fetched from some database and added to the base salary.

Via function, this can be done using the following code:

function Employee(){
let name;
let baseSalary;

var publicApi = {};

publicApi.setName = function (val){
name = val;
}
publicApi.setBaseSalary = function (val){
baseSalary = val;
}

publicApi.getName = function (){
return name;
}

publicApi.getSalary = function (){
let bonus = 1000;
return baseSalary + bonus;
}
return publicApi;
}
var emp = new Employee();
emp.setName("abc");
emp.setBaseSalary(100);
console.log(emp.getName());
console.log(emp.getSalary());
console.log(emp.name);

Inheritance:

Inheritance is an important concept in Object Oriented Programming paradigm. It’s a process by which child objects inherit the properties of the parent object. In javascript, inheritance is built into objects and every object has a property called prototype which refers to the parent of that object and this chain goes up until the prototype points to null.

As you can see above, the Obj has a default prototype property referring to Object which is a built-in data type.

Now let’s implement inheritance via classes and objects:

class Person{
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name);
}
}
class Student extends Person{
constructor(name, rollNumber){
super(name);
this.rollNumber = rollNumber;
}
logDetails(){
console.log(`Name: ${this.name}, Roll number: ${this.rollNumber}`)
}
}
var student = new Student("Heisenberg", 1);
student.logDetails(); // Name: Heisenberg, Roll number:1
student.sayName(); // Heisenberg

Student class has access to all properties of the person class.

Polymorphism:

Polymorphism is the practice to design objects in such a way that they share and override behavior from parent objects.

When a property is referred to by an object whether it’s a method or variable, the object first looks for it in its own scope, if not found then go one level above and look for it in the parent scope. It goes on until that property is found or returned undefined otherwise. This behavior of javascript objects can be utilized in such a way that child objects can override the functionality of their parent objects where required without modifying the parent.

In the above code from the inheritance, the student object calls sayName function which is not present in the Student class but in its parent, yet it is callable. We can override this function in the Student class and customize it’s behavior.

class Person{
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name);
}
}
class Student extends Person{
constructor(name, rollNumber){
super(name);
this.rollNumber = rollNumber;
}
logDetails(){
console.log(`Name: ${this.name}, Roll number: ${this.rollNumber}`)
}
sayName(){
// do stuff
console.log("From Student");
super.sayName();
}
}
var student = new Student("Heisenberg", 1);
student.logDetails();
student.sayName(); // From Student Heisenberg

The super keyword is used to refer to the parent object and call its method. Here we can do operations specific to students in the Student.sayName method and then common operations of Student and other children of Person class can be abstracted to Person.sayName method.

Association:

Association is the design principle by which different Objects can be associated with each other to perform some task. It’s of two types:

1- Aggregation:

In Aggregation, objects are loosely coupled and can independently exist. This means an object can exist even after its associated object is destroyed.

Let’s implement this via javascript:

class Wall{
constructor(width, height){
this.width= width;
this.height = height;
}
}
class Room{
constructor(wall){
this.wall = wall;
}
print(){
console.log(this.wall);
}
}
var wall = new Wall(1,1);
var room = new Room(wall);
wall = null;
room.print();

Here the wall and room object exist independently.

Aggregation is also called weak association.

2- Composition:

In Composition, objects are tightly coupled and cannot exist independently.

Here’s the javascript implementation of composition:

class Wall{
constructor(width, height){
this.width= width;
this.height = height;
}
}
class Room{
constructor(width, height){
this.wall = new Wall(width,height);
}
print(){
console.log(this.wall);
}
}
var room = new Room(1,1);
room.print();

If we destroy the room object, the wall is also destroyed as it’s enclosed inside the room object.

Composition is also called strong association.

OOP has a lot of applications and all modern software are built with design pattern based on OOP architecture. Therefore, one must have a solid understanding of above mentioned OOP concepts to build software applications.

--

--