JavaScript: A Different Kind of Object Oriented Programming and Inheritance

This tutorial assumes you know the basic syntax of JavaScript.

If you come from an Objected Oriented Programming background such as Java or c++, where you can declare class and later create objects, you might be confused by the strange ways JavaScript handles objects and inheritance. Here is the big revelation: there is no such thing as class in Javascript!

Prototype Vs Class

Despite the added syntax of ES6, including keywords such as class and constructor, JavaScript is still a prototype-based language.

A prototype is an object that your current object obj points to. When the JavaScript Interpreter cannot find a property from obj, it will continue to search in the prototype, and the prototype of the prototype, and so on, searching the entire prototype chain until it finds it or declares that the property doesn’t exist. null lies at the end of every prototype chain and signals the end of it.

myTruck → Truck* → BigCar* → Car* → Object* → null

*For simplicity, I just wrote Object( or Car, etc) here, but the correct term should be Object.prototype. I will explain this later.

Contrast this to the class-based approach of Java or Python, where you create a hierarchy of super classes and sub classes, and eventually creating one object:

class Truck → class BigCar → class Car → class Object

Truck myTruck = new Truck();

The key distinction here is that everything in the prototype chain is objects. You can assign each of them around and pass them into functions. In class-based approach, you have a bunch of classes that inherits the super class and you create only one object.

Various Ways of Creating Objects and the Resulting Prototype Chain

Object Literal

var a = {a: 1};
var proto = Object.getPrototypeOf(a);
var protoOfProto = Object.getPrototypeOf(proto);
console.log(a); // { a: 1}
console.log(proto); // {}
console.log(protoOfProto); // null

This is the easiest way to create a simple object, useful when you just want to bundle multiple data together. To get the prototype of an object, we use the function Object.getPrototypeOf(myObj); (NOT myObj.prototype! Later we will explain what it is.) Behind the scene, JavaScript automatically create a prototype chain:

a  → Object.prototype → null

If you came from a Java or Python background, you would know that all objects are sub class of the Object class. Right now, this is all you need to know about Object.prototype. All prototype chain converges to “Object.prototype → null”.

To see this,

var b = {b: 2};
console.log(proto === Object.getPrototypeOf(b)); // true

a and b both points to the same prototype.

Object.create()

Object literal is nice, but we have no control over the prototype, for JavaScript creates them behind the scene. We can gain control by

var c = Object.create(b);
c.data = 3.14159265359; // or whatever you need c for
console.log(Object.getPrototypeOf(c) === b); // true

Object.create(desiredPrototype) returns a new empty object whose prototype is whatever you pass in. Therefore, the prototype chain is c → b → Object.prototype → null.

Constructor Function—the Older ES5 Way

This is where the real fun begins. By using constructor function, we can easily create prototype chain however long we want and use it to implement complex inheritance. This way relies on a few JavaScript syntax; we will explain them one by one first.

this Keyword

Inside a function, the keyword this refers to the object calling the function.

var thisObj = {river: "flows in you"};
thisObj.referToThis = function referToThis() {
    console.log(this === thisObj); // true
}
thisObj.referToThis();

In the above code, thisObj calls the function, so inside the function this will be set to thisObj.

If a function does not belong to an explicit object, then it will be owned by the implicit global (in NodeJS, or Window if you are testing the code in browser):

function globalFunc() {
    console.log(this == global); // true
}
globalFunc();

Construct Function and the new Keyword

With the this keyword, we can use a function as a blueprint to create objects. We call those functions specifically made to create objects constructor function. This is analogous to how class is used as blueprint in Java. For instance, say we want to construct a type called Car:

function constructCar(inputModel, inputYear) {
    this.model = inputModel;
    this.year = inputYear;
}
var myCar = {};
myCar.constructCar = constructCar;
myCar.constructCar("Toyota Corolla", "2007");
console.log(myCar.model, myCar.year); // Toyota Corolla 2007

This can get tedious quickly. If we knew we have to create an empty object and modify it, why don’t we do it in one go? This is where the new comes in:

function constructCar(inputModel, inputYear) {
    this.model = inputModel;
    this.year = inputYear;
}
var myCar = new constructCar("Toyota Corolla", "2007");
console.log(myCar.model, myCar.year); // Toyota Corolla 2007

In a simplified version, the new keyword does the following:

  1. First create an empty object
  2. Run the constructor function with this bound to that empty object
  3. return that object

In addition, following convention, we should name the constructor function with the type we want (in case Car, and we capitalize it to distinguish it from normal functions.

Given this, it should be no surprise that Object and String are all actually just constructor functions.

prototype vs [[prototype]]

This part is confusing, so be ready to read multiple times. In JavaScript, there are two notions of prototype. The first  is the prototype in the prototype chain that we have been discussing. To reiterate, we can obtain this prototype via

var obj = {a: 1};
console.log(Object.getPrototypeOf(obj)); // get the prototype

The second one is a property of a function. JavaScript automatically generates it for you every time you declares a function.

function func() {
    // do whatever
}
console.log(func.prototype); // funct {}

To distinguish the two, JavaScript Specification refer to the former as “obj.[[prototype]]” and the latter as “func.prototype”.

When you create a function myFunc, myfunc.prototype is automatically generated for you. When you call new myFunc(), JavaScript creates a new object myObj using that constructor function, and set myObj.[[prototype]] to be myFunc.prototype.

To see this in action,

function Coordinate(inputX, inputY) {
    this.x = inputX;
    this.y = inputY;
}
var origin = new Coordinate(0, 0);
var pointA = new Coordinate(10, 3.14);
console.log(Object.getPrototypeOf(origin) === Coordinate.prototype);

Why do we need all this complicated prototype stuff? Recall when we use object literal, JavaScriptsets up the prototype chain behind the scene. Turns out myFunc.prototype is needed for this exact reason. The prototype chain of origin and pointA are:

origin → Coordinate.prototype → Object.prototype → null
pointA → Coordinate.prototype → Object.prototype → null

By using this prototype system, both prototype chains converges at Coordinate.prototype! origin and pointA later on can both have individual properties, and they can have shared properties by searching through the prototype chain and hitting Coordainte.prototype.

We can define shared methods that every Coordinate can perform.

Coordinate.prototype.calculateDistance = function() {return Math.sqrt(this.x**2 + this.y**2);};

When we do origin.calculateDistance();, JavaScript would not find the function in origin, so it moves on in the prootype chain, hit Coordinate.prototype, and found it.

Note that we could have put this in the constructor function:

function Coordinate(inputX, inputY) {
    this.x = inputX;
    this.y = inputY;
    this.calculateDistance = function() {return Math.sqrt(this.x**2, this.y**2);};
}

However, this approach would make a copy of the function calculateDistance on every Coordinate object we create. Inefficient!

With all these background info, we are ready to build complex hierarchy of inheritance.

function Coordinate(inputX, inputY) {
    this.x = inputX;
    this.y = inputY;
}
Coordinate.prototype.calculateDistance = function() {return Math.sqrt(this.x**2 + this.y**2);};

// represents a special type of coordinates that are on x
function CoordinateOnXAxis(inputX) {
    Coordinate.call(this, inputX, 0); // similar to super(...) in Java
    this.axis = "x-axis";
}

// set up the prototype chain
CoordinateOnXAxis.prototype = Object.create(Coordinate.prototype); //similar to the extends part of Java

// add a function for CoordinateOnXAxis.prototype
CoordinateOnXAxis.prototype.getAxis = function() {return this.axis};

// create an object of type CoordinateOnXAxis
var coolPoint = new CoordinateOnXAxis(10);
console.log(coolPoint.calculateDistance()); // 10
console.log(coolPoint.getAxis()); // x-axis

There are two things worth noting here. First, the syntax Coordindate.call(myObj, ...) means call the function Coordinate using myObj as this. This is needed because the Coordinate constructor function needs a way to get access to the this object in the outer CoordinateOnXAxis. Second, we extends the prototype chain by creating a prototype based off of Coordinate.prototype. The resulting prototype chain is

coolPoint → CoordinateOnXAxis.prototype → Coordinate.prototype → Object.prototype → null

Class Syntax – the ES6 Approach

In ES6, we have added keywords to facilitate better and clearer object oriented programming. However, the underlying mechanism is still prototype based. The following code is exactly the same as the above code.

class Coordinate {
    constructor(inputX, inputY) {
        this.x = inputX;
        this.y = inputY;
    }

    calculateDistance() {
        return Math.sqrt(this.x**2 + this.y**2);
    }
}

class CoordinateOnXAxis extends Coordinate {
    constructor(inputX) {
        super(inputX, 0);
        this.axis = "x-axis";
    }

    getAxis() {
        return this.axis;
    }
}
var coolPoint = new CoordinateOnXAxis(10);
console.log(coolPoint.calculateDistance()); // 10
console.log(coolPoint.getAxis()); // x-axis

Coordinate and CoordinateOnXAxis are still of type function. Here we use super to call the super class’ constructor.

Leave a Reply

Your email address will not be published. Required fields are marked *