JavaScript Prototype And Classes

Prototype is one of the most confusing topics in JavaScript and at the same time, it is also a fundamental topic in JavaScript that every JavaScript developer should know. Prototypes are building blocks of inheritance in JavaScript called prototypical inheritance. We'll understand all of this in detail today. Having knowledge of 'this` keyword and how class-based object-oriented programming works will help in understanding this article much better. With that said, let's start.

What is a prototype?

A prototype is an object that is used as a fallback source of properties. Every object that we create has its prototype and since arrays and functions are also considered objects, they have prototypes too. If I create an empty object -

const obj = { }
console.log(obj.toString());     // "[object Object]"

You see how we didn't create toString() method but still can access it on an empty object? If we see the properties of obj object using console. dir(obj), you will see __proto__ property inside which is an object and has a toString() method in it. So when we do obj.toString() JavaScript engine checks for toString() in obj, if it is not found then it checks in __proto__ of object obj. That is exactly why we say prototype is used as a fallback source of properties.

So whenever a new object is created, the JavaScript engine sets its prototype (__proto__). Today we don't use __proto__ to get the prototype of an object. This method is no longer used. Instead we use Object.getPrototypeOf() to get prototype of any object.

Object.create()

Using Object.create() you can create a new object with a prototype defined by you.

const myPrototypeObject = {
    toString(){
        return "You defined this method" ;
    }
}
const obj = Object.create(myPrototypeObject);
console.log(Object.getPrototypeOf(obj)); // {toString: ƒ}
console.log(obj.toString())  // "You defined this method"

So you see how easily you can create objects with prototypes defined by you. You might realize this is a very powerful feature. Now you have the ability to define a prototype and create many descendant objects with the prototype defined by you. This gives us the ability to define a basic structure of any object and create new objects with their own specific properties with the basic structure defined by your prototype much like how we do Object-Oriented Programming in other class-based languages like Java. In those languages, we define a class that defines a basic structure of any object and then create new instances of those classes in which objects have their own property values with the basic methods defined by the class.

How JavaScript leverages this feature to implement classes?

Let's define a simple function and see its properties -

function func() { }
console.dir(func); 

// arguments: null
// caller: null
// length: 0
// name: "func"
// prototype: {constructor: ƒ}
// __proto__: ƒ ()
// [[FunctionLocation]]: VM2569:1
// [[Scopes]]: Scopes[2]

You can see the above properties of the function func using console.dir(). Notice there is two properties __proto__ and prototype above. I have already talked about 'proto'. Every object has a prototype and since function is also a type of object, it also has. And remember that __proto__ and prototype is not the same. Don't worry if anything sounds confusing until here. It will get clear as you proceed further. For now, focus on prototype property of the function.

Also for a refresher - A constructor enables you to provide any custom initialization that must be done before any other methods can be called on an instantiated object.

If I define a function that will be used as a constructor then the prototype property of the constructor can be used to set the prototypes of the newly created objects.

Read the above line again once.

So If I define a constructor Rabbit and set it's prototype property to a object defined by my like this -

function Rabbit(type) {
  this.type = type;
}
Rabbit.prototype = {
     speak: function(line) {
        console.log(`The ${this.type} rabbit says '${line}'`);
    };
}
let weirdRabbit = new Rabbit("weird");
// Example from book Eloquent JavaScript

then, when this constructor will be called with new, a new object will be created and its prototype (__proto__) will be equal to the prototype of the constructor that we defined. Which basically means Object.getPrototypeOf(weirdRabbit) === Rabbit.prototype will be true.

and this is how a constructor works. Now we can create as many objects as we want with the same prototype. Internally a constructor uses the same Object.create() method to create a new object and set its prototype but by using new keyword this thing gets abstracted and we just assume a new object will get created somehow.

Classes

So now we know what is a constructor, what is a prototype, what function.prototype is used for and what is Object.create() used for. We can combine all this knowledge to create a JavaScript class and understand how that works internally.

If I create a class for the above Rabbitconstructor. This is how it will look -

class Rabbit{
    constructor(type){
        this.type = type;
    }
    speak(line) {
      console.log(`The ${this.type} rabbit says '${line}'`);
    }
}
let killerRabbit = new Rabbit("killer");
let blackRabbit = new Rabbit("black");
// Example from book Eloquent JavaScript

this is will do the exactly same thing that our previous example was doing. It's just that syntax is much better and intuitive. The code other than constructor method goes inside Rabbit.prototype.

I hope this article might have given you a better understanding of prototypes and its importance in JavaScript.

Have a good day.