Introduction to JavaScript Prototypes

Introduction to JavaScript Prototypes

What is a Prototype? Why are classes nothing but syntactical sugar and how does a class implement prototypes under the hood in JavaScript?

Listen to this article

Objects are pretty foundational in many programming languages. In this article, we will look at a few of the programming patterns for instantiating JavaScript objects, and while doing that we will try to understand the concept of JavaScript Prototypes.

So, if you guys are ready, then let's get started.

LetsGo

Let's take a look at this object vehicle. It has name and fuel as its properties and drive and refuel as its methods.

let vehicle= {};
vehicle.name = "Nano";
vehicle.fuel = 1000;

vehicle.drive = function (distance) {
    console.log(`${this.name} is driving.`);
    this.fuel -= distance;
};

vehicle.refuel = function (amount) {
    console.log(`${this.name} is refueling.`);
    this.fuel += amount;
};

Let's say if we are building an app, where we need to create multiple vehicle objects, probably with different values for their properties. Then repeating this code every time for creating a new object will take up a lot of memory.

One thing we can do is that we can take this logic and wrap it inside a function, and then use that function to create new vehicle objects.

function Vehicle(name, fuel) {
    let vehicle = {};
    vehicle.name = name;
    vehicle.fuel = fuel;

    vehicle.drive = function (distance) {
        console.log(`${this.name} is driving.`);
        this.fuel -= distance;
    };

    vehicle.refuel = function (amount) {
        console.log(`${this.name} is refueling.`);
        this.fuel += amount;
    };

    return vehicle;
}

const nano = Vehicle("Nano", 1000);
const fiat = Vehicle("Fiat", 2000);

Now, whenever we want to create a new vehicle, we can do it by just invoking the Vehicle function and also initialize it by passing the required parameters.

So, what we are doing here is that every time we are creating a new vehicle object, we are actually recreating every property and method of that object inside the memory, which is quite redundant.

All of these methods are generic to all the objects created with the Vehicle function and not specific to any particular object. So, what if there's a way to create methods only once in the memory, which is also accessible by all the objects created further.

One thing that we can do is that we can take all of these methods and put them inside a separate object and reference that object inside our Vehicle function.

const objMethods = {
    drive: function (distance) {
        console.log(`${this.name} is driving.`);
        this.fuel -= distance;
    },

    refuel: function (amount) {
        console.log(`${this.name} is refueling.`);
        this.fuel += amount;
    },
};

function Vehicle(name, fuel) {
    let vehicle = {};
    vehicle.name = name;
    vehicle.fuel = fuel;

    vehicle.drive = objMethods.drive;
    vehicle.refuel = objMethods.refuel;

    return vehicle;
}

const nano = Vehicle("Nano", 1000);
const fiat = Vehicle("Fiat", 2000);

One problem with this approach is that every time we want to add a new method we will have to define it inside the objMethods object and also reference it every time inside the Vehicle function. This will look something like:

const objMethods = {
    drive: function (distance) {
        console.log(`${this.name} is driving.`);
        this.fuel -= distance;
    },

    refuel: function (amount) {
        console.log(`${this.name} is refueling.`);
        this.fuel += amount;
    },
    newMethod: function () {
        console.log("this is a new method");
    },
};

function Vehicle(name, fuel) {
    let vehicle = {};
    vehicle.name = name;
    vehicle.fuel = fuel;

    vehicle.drive = objMethods.drive;
    vehicle.refuel = objMethods.refuel;

    vechile.newMethod = objMethods.newMethod;

    return vehicle;
}

We can use Object.create() to solve this issue.

Object.create()

Let's go through a little tutorial about Object.create().

According to MDN docs:

The Object.create() method creates a new object, using an existing object as the prototype of the newly created object.

For now, don't worry about the prototype, we will discuss that in a while.

ThankGod

In simple terms, whenever there is a failed lookup (i.e the property or method that you are trying to access is not present inside that object) on the object, the JavaScript engine is going to refer to that object which was passed as the parameter of Object.create()

ProtoBasics01.png

Here we have created the child object using Object.create() with parent object as its parameter. Even if the child object does not have a nationality property, it can still access it because after a failed lookup in the child object JavaScript engine goes to the parent object to check if there is a nationality property present, we can access it inside child object.

Coming back to the problem, every time we create a new method inside the Vehicle function we have to reference objMethods and this is quite tedious

We can solve this by creating the vehicle object using Object.create() rather than creating an empty object and then referencing the methods again and again.

const objMethods = {
    drive: function (distance) {
        console.log(`${this.name} is driving.`);
        this.fuel -= distance;
    },

    refuel: function (amount) {
        console.log(`${this.name} is refueling.`);
        this.fuel += amount;
    },
    newMethod: function () {
        console.log("this is a new method");
    },
};

function Vehicle(name, fuel) {
    let vehicle = Object.create(objMethods);
    vehicle.name = name;
    vehicle.fuel = fuel;

    return vehicle;
}

const nano = Vehicle("Nano", 1000);
const fiat = Vehicle("Fiat", 2000);

Now if we want to add new methods, we can just define them inside objMethods and we don't have to worry about referencing them inside the Vehicle function.

As we know objMethods contains shared methods of the vehicle objects and is available to all the objects created by the Vehicle function. What if there was a built-in way of implementing this functionality?

And the answer is Prototype!

Prototype

According to MDN docs:

Nearly all objects in JavaScript are instances of the Object class and a typical object inherits properties (including methods) from Object.prototype.

Every function, object, or object-type data structure has an inbuilt property called prototype which points to an object containing some built-in methods of JavaScript.

For example:

ProtoBasics02.png

ProtoBasics03.png

In simple words, " A Prototype is just a property that every function and object has which points to another object. "

ItAllComingTogether

Now instead of using objMethods to contain the shared methods, we can store them inside our Vehicle.prototype.

function Vehicle(name, fuel) {
    let vehicle = Object.create(Vehicle.prototype);
    vehicle.name = name;
    vehicle.fuel = fuel;

    return vehicle;
}

Vehicle.prototype.drive = function (distance) {
    console.log(`${this.name} is driving.`);
    this.fuel -= distance;
};

Vehicle.prototype.refuel = function (amount) {
    console.log(`${this.name} is refueling.`);
    this.fuel += amount;
};

const nano = Vehicle("Nano", 1000);
const fiat = Vehicle("Fiat", 2000);

Now let's focus on the Vehicle constructor function. The most important thing inside this function are these two lines:

  • let vehicle = Object.create(Vehicle.prototype)

  • return vehicle

The first line creates the object and looks for failed lookups and the second line simply returns the object.

There is a simpler built-in way of doing this using the new keyword.

New

The "new" keyword

Whenever we invoke a constructor function using the new keyword it creates a new instance (i.e a new object) of that constructor function.

It basically implements those two lines inside the constructor function under the hood using the keyword this without even writing those lines.

And this might look something like:

function Vehicle(name, fuel) {
    // let vehicle = Object.create(Vehicle.prototype);
    vehicle.name = name;
    vehicle.fuel = fuel;

    // return vehicle;
}

More preciously like:

function Vehicle(name, fuel) {
    // let this = Object.create(Vehicle.prototype);
    this.name = name;
    this.fuel = fuel;

    // return this;
}

Now our refactored code with the new keyword will look like:

function Vehicle(name, fuel) {
    this.name = name;
    this.fuel = fuel;
}

Vehicle.prototype.drive = function (distance) {
    console.log(`${this.name} is driving.`);
    this.fuel -= distance;
};

Vehicle.prototype.refuel = function (amount) {
    console.log(`${this.name} is refueling.`);
    this.fuel += amount;
};

const nano = new Vehicle("Nano", 1000);
const fiat = new Vehicle("Fiat", 2000);

ProtoBasics04.png

If you are coming from C/C++ or Java background, you might be thinking that this object instantiating pattern looks very similar to class implementation, and you might be wondering why JavaScript doesn't have a built-in way to implement this?

Well, you are headed in the right direction.

JavaScript does have a built-in way to implement this object instantiating pattern, but it was only introduced in ES6 in 2015. ECMAScript 6 introduced the class keyword which implements a very similar logic for instantiating objects as compared to our logic.

Oooo

Let's see our refactored code with the class keyword.

class Vehicle {
    constructor(name, fuel) {
        this.name = name;
        this.fuel = fuel;
    }

    drive(distance) {
        console.log(`${this.name} is driving.`);
        this.fuel -= distance;
    }

    refuel(amount) {
        console.log(`${this.name} is refueling.`);
        this.fuel += amount;
    }
}

const nano = new Vehicle("Nano", 1000);
const fiat = new Vehicle("Fiat", 2000);

The class keyword uses a constructor function to initialize the object properties and contains methods that are stored inside the class's prototype and then referenced inside prototypes of all the instances created using this class constructor function.

Conclusion

  • Class is nothing but a syntactical sugar that abstracts all the objects instantiating pattern logic that we discussed earlier, and saves the programmer from writing redundant code.

  • Prototypes basically save us from creating generic methods and properties for every new instance of a class.

As we all know,

Everything in JavaScript is an Object and if not, we can make it behave like an object.

And every object-type data structure in JavaScript has a prototype property.

This is why JavaScript is also called a prototype-based language.

In the next blog, I will try to explain how a prototype is used to implement inheritance in JavaScript and how is it implemented in ES5 vs ES6.

Until then, stay safe, keep coding. Byeeeee...

bye

 
Share this