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 Rabbit
constructor. 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.