JavaScript Inside Story : Everything about Prototypes and Inheritance

Mayank
|
July 15, 2021

I almost picked the title "JavaScript's Factory for Everything", but then I changed my mind after reading my older post. Just trying to push my last post here. I don't feel very comfortable writing this, not because I don't understand the stuff but because its something that pretends to be something it really is not. Protesting that prototypal inheritance is not inheritance at all does not change anything. Perhaps if it were not for the comfort of OOPers, it could have been better called prototypal wiring or maybe prototypal delegation. If you're coming from Java, how would you answer the question, "Why does Java not suppport multiple class inheritance?". Well, you'd point out the Diamond Problem, won't you? Given that JavaScript doesn't know this problem how would you explain it not being able to support such a thing? PS: It can be done but it has its own issues. Despite its appearance resembling to that of classical inheritance, JavaScript has its own reasons for (not) being able to do certain things. Not asking you to unlearn the classical inheritance take but then to understand JavaScript's way of handling prototypal inheritance you need to drop those intuitive assumptions atleast for the time being.

I dont want to recall the time when I started logging objects in the console only to check what's really inside of them. I mean I knew objects had their properties but I also found stuff like __proto__, constructor, prototype, __proto__ again. It would not stop. I kept on digging and it kept on throwing more and then I realized I was willingly stuck in a loop of references. Let me take help of an analogy to explain this to you. Lets assume that any function you create in JavaScript is a House to be put on rent. The House then comes with a bunch of keys ( prototype ). The bunch of keys has another bunch of small master keys ( __proto__ ) inside it and has a label ( constructor ) with the name of the house on it. This bunch of keys is given to the tenants ( objects created from the function ) and the tenants then keep it with them and they like to call it guess what ? __proto__. Heck! confusing. Its not that easy to build analogies here. Just take a look at the diagram I came up with.

Check out the PPT

What you see is what I am going to pen down now. Consider a class A or simply a function A. The function when created, gets two properties by default namely prototype and __proto__. __proto__ is a property that is available on everything in JS be it a primitive or an object. Go ahead! try that in your console. It contains some information from the function that was responsible for creating the primitive/object in the first place. Since a function is nothing more than a Function object, the __proto__ property on each and every function takes its value from Function.prototype. What about the prototype property? In contrast with __proto__ which is available on anything and everything in JS, prototype is only available on JavaScript functions. The prototype property is an object ( only exception being Function.prototype which is a native function ) with two default properties namely constructor ( refers to the Function/Class itself to which prototype belongs ) and __proto__ . The purpose of __proto__ on A.prototype is nothing different from that on the function A itself. A.prototype.__proto__ contains information about the function that was responsible for the creation of A.prototype. Since this object (A.prototype) was automatically created, the function/class responsible for its creation was Object. No wonder why every SomeFunction.prototype.__proto__ gets a default value of Object.prototype. To verify this, try creating an object using the object lietral syntax like so.

Try on Codepen

Moving on to instantiating the constructor function, lets do let objA = new A(); to create an object from the function A. objA gets a __proto__ . We just discussed how everything in JS gets this default property with a value of SomeFunction.prototype, SomeFunction being the function/class responsible for its creation. No points for guessing its value in this case. Its A.prototype.

.prototype only exists on functions. Try on Codepen.

Prototypal Inheritance

All this while what I've been trying to tell you is that __proto__ is just the pen name of prototype. A constructor's prototype becomes it's object's __proto__ . How does this help? Well since its not the copy but the reference to a function's prototype which is shared amongst objects created using the function, any new function/property on the prototype of a function is readily available on the object's __proto__ as well. Though its not a good practice to monkey patch properties on the protoype of a constructor function. Read more about that here. Interestingly you dont even need to access the patched property via __proto__ . You just access it on the object like objA.somePatchedFunction() and it gets resolved from __proto__ in the chain. This sounds fun but can very quickly get on the nerves when some object starts patching functions/properties on its __proto__ property causing a prototype namespace pollution.

Anyway, did you think of this? What happens when you manually alter the __proto__ property on objA like objA.__proto__ = { random : 10 } ? Apparently, the link from the function A breaks and you can no more access the functions patched on A.prototype and then you get access to properties of the newly set object ({ random : 10 }) like objA.random. Apart of assigning a value directly to objA.__proto__ there exist lawful functions (Object.setPrototypeOf, Object.create) in JavaScript to help you do this some of which are on the verge of being deprecated but that's not my concern for this blog post. This does not even seem very helpful. Why don't we try altering the __proto__ property on A.prototype? Well, that sounds like a plan.

Try on Codepen

For the ease of understanding lets try to alter Child.prototype. Let me tell you what am I trying to do. When I create a new object using the Child constructor function, I can say something like new Child().c and get the expected value i.e. 200. What I want is to get a valid value on doing so new Child().patchedP ie. 100. Do you think I should simply make an assignment like so Child.prototype = Parent.prototype ? Well , no because then when you want to patch some functions on Child.prototype, you will end up patching the orignal Parent.prototype. Changes on Child should not impact Parent or you can't call it inheritance. I better use an intermediary object to set the prototype of child. That's why we do this Child.prototype = Object.create(Parent.prototype). Now when you patch the Child's prototype, it won't impact the Parent (you only patch the intermediary object).

Did you notice the call to Parent function within the Child function (kind of super if you're coming from Java) ? Try commenting it out in the pen. That will leave you with no access to Parent's instance properties i.e. p here. When you call the Parent with 'this' (this refers to the newly created object when you say new Child()), the Parent function executes to add the property p on new Child(). Now in every new instance you create from the Child function, you get access to instance properties of Parent & Child both along with patched properties of both Parent.prototype & Child.prototype. Additionally, now patching Child.prototype will not impact Parent. Now that's something we can kind of call Inheritance. Just to touch upon the concept of prototype chaining, it goes without saying that if you try to access aChild.randomProperty given aChild = new Child(); , it shall be first looked up in the property list of  aChild itself, if not found it should be searched for in aChild.__proto__ (the intermediary object we discussed earlier), next in aChild.__proto__.__proto__ until the search reaches Object.prototype which is the last man standing in the prototype chain.

A look at new Child()

Take Away

1- Every function's .prototype property is of type - object except function Function. (It's .prototype property is of type - function)

2- Every function's .__proto__ property is always equal to Function.prototype and hence of the type - Function.

3- Objects don't have .prototype property.

4- Every object's .__proto__ property is of type object.

5- An object's .__proto__ property takes its value from the .prototype property of the Function from which it was created.

6- If an object was not created using any particular function (created using object literal or using Object.create(Object.prototype)), the value of it's .__proto__ property will be Object.prototype.

7- Create an object from a class A or a function A : let objA = Object.create(A.prototype); or let objA = new A();

8- In ES5 inheritance should look like so : let anObjectFromParent = Object.create(Parent.prototype); Child.prototype = anObjectFromParent;

9- In ES6, the extends keyword plays the role of Object.create(Parent.prototype) and the super keyword invokes the constructor of the parent.

10- Accessing __proto__ directly on an object is not as optimal as using the new keyword, Object.create (to set) and Object.getPrototypeOf (to get).

11- __proto__ is just a way to programmatically access an object's [[Prototype]] internal slot which is otherwise not accessible in code.

written by
Mayank
Web Technology Enthusiast