DEV Community

Hrishikesh
Hrishikesh

Posted on

Prototypes

Inheritance in JavaScript is not like other famous OOP languages such as Java, C# etc. Coming from Java / C# background to JavaScript , I found it quite confusing at first to understand how this type of inheritance works or how prototypes work in general. This is my attempt to simplify it as much as I can. This will be a long read. There's summary all the way to bottom, if you are just revising the concept.

Basics

Let's start from the very basics, let's create a simple object called bird and display it's internals using special method provided by Console called dir().

const bird = { flies : true }; 
console.dir(bird);
Enter fullscreen mode Exit fullscreen mode

Following is printed to console
 raw `[[Prototype]] intro` endraw

We can see our flies property, but there's another property present that we didn't define : [[Prototype]]. If you try to access this property in code, it won't work, because these properties are hidden or internal to JS engine. We can see that the [[Prototype]] property is an Object. Let's look inside that object.

 raw `[[Prototype]]` endraw

We see so much stuff in there. Some of it we probably never use. There are some familiar methods visible like the toString method or valueof. All this stuff is available to our bird object through this [[Prototype]]. That is why when we try to use it like so on bird,

console.log(bird.toString());
Enter fullscreen mode Exit fullscreen mode

It doesn't throw any errors. Although we didn't define toString ourselves on bird, it's available to use because it's present in this hidden property [[Prototype]].

Now even if the [[Prototype]] itself is hidden, we have been provided a special property __proto__ that can be used to tap into it. Let's see it in action.

console.log(bird.__proto__);
Enter fullscreen mode Exit fullscreen mode

prints the same object we saw earlier.

 raw `printing __proto__ of bird` endraw

This __proto__ is actually a Getter to read [[Prototype]], naturally there's Setter with same name to set value to [[Prototype]] if we wanted to.

Let's create a parot object and set it's [[Prototype]] as our bird object.

const parot = { speaks : true} ;

parot.__proto__ = bird;

console.dir(parot);
Enter fullscreen mode Exit fullscreen mode

We see something like this ,

chain of prototypes

It has formed a chain of [[Prototype]], which ends all the way back to our original [[Prototype]] object. Because of this chain we can access flies property on parot as well as toString from the original [[Prototype]].

console.log(parot.flies);
console.log(parot.toSting());
Enter fullscreen mode Exit fullscreen mode

Prototype chain 2

If you have property defined with same name as in prototype, you are essentially overriding the property in [[Prototype]]. When accessing that property, JavaScript will do what is called 'Short circuiting' the chain to return you nearest value.

parot['toString'] = function () {
  return JSON.stringify(this);
};

parot.toString();
Enter fullscreen mode Exit fullscreen mode

toString override

You no longer see '[object Object]' but you see '{"flies":true}' so it called toString from parot itself now.

This chain must end somewhere right? and it does, in the original or you can call it the default [[Prototype]]. This default [[Prototype]] comes from Object.prototype. We will see it in next section.

Object.prototype

It is the default object that is set as [[Prototype]] when you create an object.

console.log(Object.prototype);
Enter fullscreen mode Exit fullscreen mode

and you will see the same object we saw in birds's [[Prototype]].

Object.prototype

you will notice [[Prototype]] is not present on this default object , because it's null.

Object.prototype.__proto__

To avoid the confusion we are going to get later when we see constructor functions, I will refer to [[Prototype]] as 'prototype' from now on.

This chain of prototypes is how the inheritance works in JavaScript. Properties of the prototype are inherited by our object. Please note that the prototype can only be set an Object or null, it will not allow any other type to be set as prototype.

__proto__ not recommended

I will not go into this rabbit hole as to why , but the use of __proto__ is no longer considered a good practice, instead we can use methods on Object to do same thing.

  1. Object.setPrototypeOf(obj, obj2)

  2. Object.getPrototypeOf(obj)

1st one sets obj2 as prototype of obj and 2nd one returns prototype of provided obj.

Write operations are not affected by prototype

Let's take an example to understand this.

const bird = {
  flies: true,
  _isFree: true,
  set isFree(value) {
    this._isFree = value;
  },

  get isFree() {
    return this._isFree;
  },
};
const parot = { speaks: true };

Object.setPrototypeOf(parot, bird);

parot.isFree = false;

console.log(parot.isFree);
console.log(bird.isFree);
Enter fullscreen mode Exit fullscreen mode

First we defined a bird object with getter and setter methods to set value of property _isFree.Then we created parot object and set bird as prototype of parot. We proceed to calling isFree the setter, on parot and set the value to false.

Calling the setter isFree on parot is a write operation and naturally the question appears in mind, will it change the _isFree property of the bird object ? because the setter method is defined on bird and not on parot.

The answer is , it doesn't. Instead it creates _isFree property on parot and sets it's value to false. _isFree property on bird is not affected.

write operation result

Technically what this means is, value of this is not affected by prototype. The value of this when calling isFree on parot, is the parot object. It behaves as expected, value of this is taken from calling context.


Constructor Functions

In earlier section we saw how prototypical inheritance works but how do we actually put it to some practical use? We saw in the last example where we set some getter setters on bird that were reusable on parot but is there a way were we can make this generic to create objects with some blueprint or structure and add methods that are reusable? The answer is yes, with the help of Constructor functions!

Here's how we create a constructor function,

function Bird(flies, isFree) {
  this.flies = flies;
  this._isFree = isFree;
}

const parot = new Bird(true, false);
Enter fullscreen mode Exit fullscreen mode

a constructor function is just like your normal function that accepts arguments and set's the argument values to this.To create an object using constructor function we call the function with new keyword. Here's where things get little complicated. It sets prototype i.e [[Prototype]] of the object you create to an object that exists in prototype property on it.

Let's demystify that last statement. Each constructor function or rather any function in JavaScript has a prototype property on it.

console.log(Bird.prototype);
Enter fullscreen mode Exit fullscreen mode

constructor function 1

you can see there's a property called constructor which contains the reference to itself i.e Bird function and [[Prototype]].
We can easily prove that above object is set as [[Prototype]] for newly created parot,

console.log(Object.getPrototypeOf(parot));
console.log(Object.getPrototypeOf(parot) === Bird.prototype); 
Enter fullscreen mode Exit fullscreen mode

constructor function 2

constructor function 3

This means we can define properties on Bird.prototype that will be accessible to parot. Let's do that and change code code like so,

function Bird(flies, name) {
  this.flies = flies;
  this.name = name;
}

Bird.prototype.flying = function () {
  console.log(`${this.name} is flying`);
};

const parot = new Bird(true, 'parot');
parot.flying();
Enter fullscreen mode Exit fullscreen mode

and you will see 'parot is flying'. This is how we can put the prototypical inheritance to some use.

By the way did you realize something? recall the Object.prototype we saw way before at the start. Yes, Object itself is a constructor function!


Native Prototypes

Now that we have better understanding of constructor functions and [[Prototype]]. Let's discuss what are native prototypes.

const user = {name: "hrishi", age: 26};

console.log(Object.getPrototypeOf(user)); 
Enter fullscreen mode Exit fullscreen mode

and you shall see the default [[Prototype]] value we have been seeing. It is taken from Object.prototype. Internally when you use {...} syntax is it calling new Object() and assigning the value in Object.prototype to [[Prototype]] of your new object. We can easily prove that.

console.log(Object.getPrototypeOf(user) === Object.prototype);
Enter fullscreen mode Exit fullscreen mode

Native prototypes 1

Other build-in types like Array, String, Number follow the same thing. They are constructor functions with some methods on them that can be reused on instances you create.
for example,
startsWith, toUpperCase methods on strings are defined on String.prototype.
map, filter , includes methods on the arrays are actually defined on Array.prototype and so on.


Summary or TL;DR

  • All the objects in JavaScript have a hidden property [[Prototype]] which itself is another object or null.
  • This property can be accessed or set using __proto__ getter and setter methods available in [[Prototype]].
  • It is no longer considered a good practice to use __proto__, rather Object.setPrototypeOf and Object.getPrototypeOf should be used.
  • If a property does not exist on the object, JavaScript looks for it in the prototype chain and fetches it wherever it is available nearest to the object.
  • this and write operations are not affected by prototypes.
  • Constructor functions allows us to create objects using new syntax that sets [[Prototype]] of newly created object to an object in Constructor.prototype. This only happens when function is called with new otherwise there's no such effect.
  • All built in types such as Array, String, Number,Date... are constructor functions that define methods usable on objects we create using them.
  • Object itself is a constructor function , all other built in constructor functions have their [[Prototype]] = Object.prototype.

I hope this helped you understand prototypes better. I will let you explore from here on out.

Top comments (0)