JavaScript – Object Oriented Programing

Object Oriented Programing means bringing together the data and the behavior in a single location called an object. data and behavior are the 2 main things in programs. data means the things we keep in memory and behavior means functions, the things that programs can do. This paradigm is entirely different than the functional programming paradigm that keeps a separation between the data and behavior.

JavaScript is multiparadigm, we can use the Functional Programing paradigm and the Object Oriented Programming paradigm. We can use both of these techniques to make it easy to reason about, understand, extend, and be more efficient.

Before You keep reading this article, I recommend that you read one of my previous articles named “JavaScript Supe Powers – Prototypal Inheritance” because prototype inheritance is why we can achieve Object Oriented techniques in JavaScript.

In the next section we will go step by step to understand the different techniques to write object oriented code.

Encapsulation and Sharing

Let’s create 2 objects:


const person1 = {
  name: 'Ran',
  age: 30,
  profession: 'teacher',
  sayYourType() { 
    return `I am a ${person.profession}`
  },
}

const person2 = {
  name: 'Dave',
  age: 32,
  profession: 'teacher',
  sayYourType() { 
    return `I am a ${person.profession}`
  },
}
console.log(person1.name);  // Ran  
console.log(person1.sayYourType());  // I am a teacher

console.log(person2.name);  // Dave
console.log(person2.sayYourType());  // I am a teacher

What we did basically is encapsulation which is the first step in OOP. we have encapsulated functionality that can be contained to model the real world into containers.

In the above example, we have created 2 objects, each with properties which are the state or data, and a method which is the functionality that can act upon the state. notice that the method in person 2 is a copy of method in person1 including the object and type of person1 which are the same in person 2.

The problem with the above example is that for each object that we create we will have to add the same properties and methods. we will have to repeat ourselves over and over for each object that we create.

Let’s try to get the same result from the code above by using factory functions. factory functions are functions that create objects.:


function createPerson(name, profession) {
  return {
    name,
    profession,
    sayYourType() { 
      return `I am a ${profession}`
    },
  }
}

const person1 = createPerson('Ran', 'teacher');
console.log(person1.sayYourType());  // I am a teacher
const person2 = createPerson('Sam', 'programmer');
console.log(person2.sayYourType());  // I am a programmer

With this code we moved one step further to OOP, we avoided repetitive code. But we still have a problem here. This way is not memory efficient. More objects that we will create (that have basically the same functionality) more memory we will use. With 100 objects that have one method for example like in the code above, we will have 100 functions that sits in memory. luckily we have a prototypal inheritance in JavaScript and we can use it to overcome the problem we are facing in the above code. With prototypal inheritance, we can share the functionality across objects.

Let’s see an example of how we can share functionality between objects without prototypal inheritance 


const personFunctions = {
  sayYourType() { 
    return `I am a ${this.profession}`
  }
}

function createPerson(name, profession) {
  return {
    name,
    profession,
  }
}

const person1 = createPerson('Ran', 'teacher');
person1.sayYourType = personFunctions.sayYourType;
console.log(person1.sayYourType());  // I am a teacher

const person2 = createPerson('Sam', 'programmer');
person1.sayYourType = personFunctions.sayYourType;
console.log(person2.sayYourType());  // I am a programmer

We have created an object and added the shared functionality inside a specific object and used it in the objects and by that, we have created shared functionality.

You can see that we adding the functionality for each object which is a repepetive code and hard to maintain. let’s see how we can make a small cleanup on the above code by using object.create:


const personFunctions = {
  sayYourType() { 
    return `I am a ${this.profession}`
  }
}

function createPerson(name, profession) {
  let newPerson = Object.create(personFunctions);
  console.log(newPerson);  // {} (an empty object created by Object.create)
  console.log(newPerson.__proto__);  // { sayYourType : [Function]} (Object.create creates a prototype chain for us)
  newPerson.name = name;
  newPerson.profession = profession;
  return newPerson;
}

const person1 = createPerson('Ran', 'teacher');
console.log(person1.sayYourType());  // I am a teacher

const person2 = createPerson('Sam', 'programmer');
console.log(person2.sayYourType());  // I am a programmer

Object.create creates a link between the object that it excepts (personFunctions) with the new object that has been created (newPerson) and this is possible because under the hood it uses prototypal inheritance. This is not exactly OOP so let’s see how we did it before Object.create came to JavaScript.


function CreatePerson(name, profession) {
  this.name = name;
  this.profession = profession;
}

CreatePerson.prototype.sayYourType = function() {
  return `I am a ${this.profession}`
}

const person1 = new CreatePerson('Ran', 'teacher');
console.log(person1.sayYourType());  // I am a teacher

const person2 = new CreatePerson('Sam', 'programmer');
console.log(person2.sayYourType());  // I am a programmer

In the above example, we have used a constructor function to create objects. every function that we invoke with the “new” keyword is called a constructor function and one of the conventions is to put a capital letter to the name of the function to let the other developers know that they have to invoke it with the “new” keyword. The “new” keyword automatically creates an object, returns the object and modifies the “this” keyword to point to the created object (instance). With the prototype property, we added shared functionality to all the objects that were created by the constructor (the calling objects).

The issue with this kind of code is that it’s hard to understand, the prototypal inheritance is not straightforward and this is why the “Object.create” was added to the language in the first place, to make it more simple, to avoid that kind of code and use “pure prototype inheritance”.

The use of constructors is not pretty and not simple as we saw in the last example, so to overcome that issue the classes came to the language in ES6. with a class we are able to contain data and functionality in the same environment. classes bringing us even closer to OOP.  let’s see an example:


class Person {
  constructor(name, profession) {
    this.name = name;
    this.profession = profession;
  }

  sayYourType() {
    return `I am a ${this.profession}`
  }
}

const person1 = new Person('Ran', 'teacher');
console.log(person1 instanceof Person);  // true
console.log(person1.sayYourType());  // I am a teacher

const person2 = new Person('Sam', 'programmer');
console.log(person2 instanceof Person);  // true
console.log(person2.sayYourType());  // I am a programmer

In the example above we have created a class, the class has a constructor that contains the data and it has methods that act upon the data. With the “new” keyword we instantiate an object. the class basically acts as a blueprint that creates instances (objects) for us.

Classes are syntactic sugar. under the hood, we still using prototypal inheritance, we still using the “new” keyword with the prototype. we are not using classes as are used in other languages. and this is the closest that JavaScript is going to get to classes.

Inheritance

After we covered the encapsulation technique in Javascript, let’s understand how to achieve the second technique which is inheritance.


class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sayName() {
    return `My name is ${this.name}`
  }
}

class Teacher extends Person {
  constructor(name, age, title) {
    super(name, age);
    this.title = title;
  }

  sayProfession() {
    return `I am a ${this.title}`
  }
}

const teacher1 = new Teacher('Jim', '40', 'professor');
console.log(teacher1 instanceof Person);  // true
console.log(teacher1 instanceof Teacher);  // true
console.log(teacher1.sayName());  // My name is Jim
console.log(teacher1.sayProfession());  // I am a professor

We created a class that extends from another class and this is called sub-classing in classical object-oriented programing. We have a base/super class and a subclass. 

Let’s break down the things that happens here:

  • The extends keyword – extend and set the prototype that is __proto__ to point the subclass. It means that the subclass will have a prototype chain to the base/super class.
  • The subclass constructor – runs only when invoking the subclass with the new keyword
  • The super keyword – if we want to run inside the subclass constructor anything with the “this” keyword (like the “title” in our example), we need to use super. The super will invoke the base/super class constructor.  basically the super creates calls the super class and creates an instance and on that instance, we can add more specific properties for the subclass. 

Let’s see in depth whats happening under the hood:


/* According to the code in the last example */

// The Teacher is a constructor function (not an object)
console.log(Teacher.isPrototypeOf(teacher1) // false

// Object.prototype is the object that contains all the properties and methods we have available 
console.log(Teacher.prototype.isPrototypeOf(teacher1) // true

// Again, Teache is a constructor function and not an object
console.log(Person.prtotype.isPrototypeOf(Teacher)); // false

console.log(Person.prtotype.isPrototypeOf(Teacher.prototype)); // true

/* The prototype is confusing, lets see how we can do it more simpler */
console.log(teacher1 instanceof Person);  // true
console.log(teacher1 instanceof Teacher);  // true

instance is when we use the “new” keyword from a class, we create an instance of a class. inheritance which is what we do with the keyword “extends”, is inheriting something from a higher class. with inheritance JavaScript doesn’t copy our functionality, instead, it links up the prototype chain. so we are not creating copies which will make the code inefficiant.

Now we know how to reuse code and extend the classes to have their own individual unique things like properties and methods 

Polymorphism

Polymorphism means the ability to process objects differently depending on their data type or class. We can redefine methods for derived classes which allows us to reuse some of the functionality but also customize methods to their own objects and classes.

Let’s see an example of method overriding, the same method will act differently for each type of a class:


class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sayName() {
    return `My name is ${this.name}`
  }
}

class Teacher extends Person {
  constructor(name, age, title) {
    super(name, age);
    this.title = title;
  }
  // We copied the function from super class and changed the behaviour
  sayName(lastName) {
    return `My name is ${lastName}`
  }

  sayProfession() {
    return `I am a ${this.title}`
  }
}

const teacher1 = new Teacher('Jim', '40', 'professor');
console.log(teacher1.sayName('smith'));  // My name is smith

Let’s see an example of method overloading by adding extra features or parameters to a method, the same method will act a bit differently for each type of class:


class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sayName() {
    return `My name is ${this.name}`
  }
}

class Teacher extends Person {
  constructor(name, age, title) {
    super(name, age);
    this.title = title;
  }
  // We copied the function from super class and changed the behaviour
  sayName(lastName) {
    return `${super.sayName()}, Mr ${lastName} for you.`
  }

  sayProfession() {
    return `I am a ${this.title}`
  }
}

const teacher1 = new Teacher('Jim', '40', 'professor');
console.log(teacher1.sayName('smith'));  // My name is Jim, Mr smith for you.

Summery

I hope that by now you can understand how we can use Object Oriented Programming paradigm to make well organized and better code. we talked about the 4 principles which are:

  • Incapsulation: instead of procidual programing which are functions that modify data with no real structure, we can put the code in an objects and organize it as units that model real world applications and make them interact with each other which help us to maintain code and make it reusable.
  • Abstraction: hiding the complexity from the user by creating simpler interfaces. we create a class and all we need to do is instantiate it and we get all the functionality out of the box which reduces complexity.
  • Inheritance: creating object that can inherit functionality from other objects we can avoid repeatition of the same code and saving memory space by using shared methods.
  • Polymorphism – we can call the same method on different objects and each object responding in different way. prevent us from copieng code over and over, we can reuse some of the functionality fro a super class to adapt to our own specific needs.

With this 4 principles we can write DRY code, clear code, extendable code, maintainable code which is memory efficiant.