In the Beginning was the Prototype : Understanding OOP in JavaScript

In the Beginning was the Prototype : Understanding OOP in JavaScript

Object oriented programming is a computer programming pattern that organizes software design around objects and classes rather than function or logic. Objects store data in the form of properties and logic(functions) as methods. Classes are the blueprints of an object. They describe the shape of objects. While objects are instances of these classes.

Concepts of Object Oriented Programming

There are four major oop concepts:

  1. Encapsulation: This is the process of dividing programs into smaller pieces in the form of objects & classes and making each piece responsible for managing it’s own state.
  2. Abstraction: This is an extension of Encapsulation. Here the programmer hides all details of objects except the relevant data needed by outer code in order to reduce complexity.
  3. Inheritance: The process of making code reusable by creating an hierarchical program structure where subclasses can inherit methods and data from parent classes
  4. Polymorphism: This is somewhat an extension of inheritance. Here, the programmer makes code and methods of parent classes adapt to different interfaces of all the subclasses that inherit from it.

Object Oriented Progam Design in JavaScript

Just as in many other programming concepts, JavaScript adopts a minimalistic approach to object oriented programming. It implements OOP using constructors and prototypes.

Constructor

A constructor provides us with something like a class definition enabling us to define the shape of an object, including any method it contains. It is simply a function that returns and object.

function Man() {
  this.name = "Albert";
  this.age = 42;
}

It can be defined with parameters that shape our object. To create an object instance of a constructor, we call it with the new keyword passing in data as arguments specific to our new object instance.

function Man(name, age) {
  this.name = name;
  this.age = age;
}

const david = new Man('David', 22)
console.log(david.name) //David
console.log(david.age) //22

Constructors provide a way to implement object oriented concepts such as encapsulation and abstraction.

Prototypes

const myObj = {}
console.log(myObj.toString()) //[Object Object]

Wow I brought out a property out of nowhere? No not really.

Prototypes are the mechanism by which JavaScript objects inherit features from one another. Every object in JavaScript has a built in property called it’s prototype. A prototype is another object that is used as a fallback source of properties. A prototype being an object also has it’s own prototype.

When an object gets a request for a property that it doesn’t have, it’s prototype will be searched, then the prototype’s prototype and so on. So who is the prototype of an empty object? The great ancestral prototype, the entity behind all objects in JavaScript, Object.Prototype. And the prototype behind it? Null.

The prototype relation of JavaScript objects form a tree shaped structure and at the root of the structure sits Object.Prototype providing a few methods that show up in all objects such as toString.

Many objects don’t directly have Object.prototype as their prototype but instead have another object that provides a different set of default properties. Functions derive from Function.prototype, and arrays derive from Array.prototype.

Finding and Object's Prototype

The Object.getPrototypeOf() method returns the prototype (i.e. the value of the internal [[Prototype]] property) of the specified object.

const myObj = {}
console.log(Object.getPrototypeOf(myObj) === Object.prototype ) //true

Note: objects dont have a direct property that represents their prototype. Although some browsers have a .__proto__ property that represents an object's prototype, It is not a standard and it's use is discouraged. However constructor functions have a constructor.prototype property that represents the prototype of any object that is an instance of it.

function Man(name, age) {
  this.name = name;
  this.age = age;
}

const david = new Man('David', 22)
console.log(Object.getPrototypeOf(david)===Man.prototype) //true

Note: A constructor's(or function's) prototype property is not the same as a function's prototype. A function's prototype is Function.prototype (which all functions inherit from) while a constructor's prototype property (constructor.prototype) is just an arbitrary property that represents what will be assigned as the prototype of objects that are instances of it ( called with the new keyword). To reiterate, all functions (except arrow functions) have a prototype property, this is not the same as the function's prototype which it inherits from. This is just an unfortunately named property that is only important when the function is called as a constructor (with the new keyword). I know, JavaScript is an insane language

SETTING AN OBJECTS PROTOTYPE

When creating an object, we can set the prototype

  1. Creating an object with object literals ({}) set it’s prototype to Object.prototype.
    const myObj ={} 
    //is the same thing as
    const myObj = new Object()
    
  2. Creating an object with a constructor sets the prototype of that object to constructor.prototype which contains all the methods defined on the constructor

  3. We can create an object using Object.create and pass in what we would like to be it’s prototype as an argument

function Man(name, age) {
  this.name = name;
  this.age = age;
}
const david = Object.create(Man.prototype)
console.log(Object.getPrototypeOf(david)===Man.prototype) //true

PROTOTYPAL INHERITANCE

Let’s say we where to build a program that represents all the individuals in a college. This is how we would go about it

function Person(name, age) {
      this.name = name;
      this.age = age;
      this.sayName = function(){ 
            return "my name is " + this.name " and I am " + this.age + " years old"
        }
  }

We give data properties like names and age and we could also add methods.

But we might want to differentiate between professors and students and give professors special properties. We could do that by defining two seperate constructors for each, but a downside to this is in a case where there are many shared properties, we will end up repeating a lot of code and this of course is discouraged. A better way would be to define a Person constructor, this would hold all the common properties shared between everyone in the college and then two seperate constructors for professors and students alike which would inherit all the shared properties from the Person class.

function Person(name, age) {
      this.name = name;
  }
Person.prototype.introduceSelf = function(){
          console.log(“my name is ” + this.name)
}

function Professor (name){
     Person.call(this, name)
//refer to my blogpost on 'this' keyword to understand what happened
}

Professor.prototype = Object.create(Person.prototype)
/*Recall that constructor.prototype is just an empty object and the only way to “inherit” from another object is to make that object it’s prototype*/

const david = new Professor('David', 32)

david.introduceSelf()
//my name is David

Prototypal Polymorphism

We might want to tweak some parts of our Professor class for each object to add a professor title when sayName is called. We could delete sayName from the Person class and define a new SayName method in all seperate subclasses that inherit from Person class.
However if there are many other subclasses that inherit from Person class and sayName works the same in all other classes, it would be unnecessary code repetition to define it in all subclasses, we can leave it in the Person class and instead tweak the sayName method in the Professor subclass. Tweaking how a method works depending on the instance is code polymorphism.

Professor.prototype.sayName = function(){
   console.log(“I am Professor ” + this.name)
}

JavaScript prototypes coupled with constructors provide an implementation of object oriented programming but when building complex programs, prototype chaining and inheritance can become very tricky. With es6 JavaScript introduced a class based system layered on top of prototypes and constructors

ES6 CLASSES

In ES6, JavaScript introduced classes to the language. This is an attempt to mimic lower level languages implementation of object oriented programming. Note, under the hood Es6 classes use prototype, they just make it much more easier to set up prototype chain and other OOP concepts

Defining a Class

We define a class by using the class keyword followed by curly braces. Inside the class, we define a constructor function using the constructor keyword. This holds the properties of the class and outside the constructor function(still inside the class), we define the methods. Calling the class using the new keyword runs the constructor function and returns an object that is an instance of the class.

class Person {
    constructor (name,age){
        this.name = name,
        this.age = age
      }
   }
const david = new Person(“david”, 22)

Classical Inheritance

With ES6 classes, it is much easier to inherit from parent classes. We do that using the extends keyword followed by the parent class we want to inherit from. Here is a redo of the previous example. Note: When we inherit from a parent class, we have to call the parents constructor function so we can have access to its properties. This is done by calling a function called super() inside the constructor of the subclass.

class Person {
    constructor (name,age){
        this.name = name,
        this.age = age
      }
   }
class Professor extends Person {
   constructor (name,age,role){
       super(name,age)
       this.role = role
      }
}
const david = new Professor(“david”, 22, “head of department”)

Note : this is not an exhaustive tutorial on ES6 classes but just the basics to help you understand Object oriented programming in JavaScript