Of prototypes, prototypes, prototypes, and prototypes in Javascript.
--
That’s a lot of prototypes. I know. Not my fault.
Disclaimer: This article is an attempt at clarifying the concept and the uses of the word prototype in JavaScript. I am a student at Launch School and some inaccuracies are possible.
The difficulty with prototypes in JavaScript is that the word prototype is used everywhere sometimes to designate different things (or the same thing, who knows?). For example, let’s say you are discussing some concepts OOP concepts with a friend.
He keeps using words like “prototype object” and an “object prototype”. What is he talking about? Chances are, he has no clue either. But you’ve heard of a few other “prototypes”. What does your friend mean by prototype? [[Prototype]]
? __proto__
? Prototype property of the constructor? A mix of all the above?
Let’s try to classify all the potential prototype’s meanings. We have 1 concept and 3 properties:
- A prototype, a.k.a. the prototype concept
[[Prototype]]
, a hidden property on an object, the prototypal chain link.prototype
, prototype property, or the dot prototype property as I like to call it__proto__
, the mysterious proto, a.k.a. the dunder proto, that you are not supposed to use
Conveniently enough, we will use this order to take a closer look at the different ideas behind the prototypes. Let’s start with the mother of all prototypes, the general concept of a prototype.
I- What is a prototype?
For the general concept of prototype, there is a simple definition we can use here, a prototype is an object from which another object inherits some properties. In other words, a prototype gives an inheriting object access to its properties. So far so good. We could stop here, unfortunately, we won’t.
Do all objects have a prototype?
In a way, they do. But where there’s an object with a prototype, there is an object with a null prototype at the end of the prototypal chain. The existential question we can ask here is: is a null
prototype a prototype? Or is it an absence of a prototype? We’ll leave the debate at that, and move on to something more pragmatic.
“First there was null, and then came the prototype” — the gods of the prototypal chain
Let’s create an object with an object literal, just to see if it does have a prototype:
> let obj = {};
> Object.getPrototypeOf(obj);
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
obj
has a prototype that looks like an object with some properties in it. So, an empty object created with the object literals does have a prototype. Some link has been set up without us noticing it. Now, what is the prototype of the prototype?
> Object.getPrototypeOf(Object.getPrototypeOf(obj))
And……
null
Anti-climatic. The prototype of the prototype is null
. So objects have a prototype, except, when they don’t, in which case, the prototype is set to null
.
Can we set a prototype to anything we want?
We could try to set the prototype to something else. Let’s go and try to set the prototype of the object obj
to undefined
.
> Object.setPrototypeOf(obj, undefined)
And……
Uncaught TypeError: Object prototype may only be an Object or null: undefined
No, we can’t set an object’s prototype to anything we want, it’s not a property like any other property. It’s a property that can be only set to an object or null
.
We can take a larger view here and see the relationship with the chain: a prototype chain is made of objects linking to other objects until an object’s prototype is null
.
Let’s review this and see this from another angle. We know that we can create objects with the object literal {}
, new Object()
or by using Object.create()
. When we use object literal or new
, how do we set the prototype to something? What is this something? When we use Object.create
, we know that we set the prototype of the object to the passed-in object, but what does the chain actually look like? Let’s keep these questions in mind and move forward. Here is what we know so far, which could be summed up as, not much, or more precisely, objects have prototypes:
“OK, OK, but if we have a prototypal chain, what are the links of the chain made of? How does an object know what its prototype is?” You ask good questions, I like your questions. The answer lies with [[Prototype]]
, or the “bracket” prototype property as I like to call it.
II — What is [[prototype]]
?
The “bracket” prototype properties are the links of our prototype chain. [[prototype]]
is an internal, hidden, property present on every object (yes, that includes functions, functions are objects, one time). As we all should know, it can not be accessed through the method hasOwnProperty
, because it is hidden. Let’s test that:
> let veggies = {};
> veggies["tomato"] = 3;
> console.log(veggies.hasOwnProperty("[[Prototype]]"));
false
No luck here, it seems that there is no [[prototype]]
on the object. Still, if we want to see it, after all, seeing is believing, we can. If you just type the name of an object in the browser console (here in Chrome), you get a list of the object properties. And, lo and behold, our mysterious [[Prototype]]
appears.
> veggies
{tomato: 3}
tomato: 3
[[Prototype]]: Object
See? That was easy. Here is our not-so-elusive animal in the wild. The guy in charge of hiding the [[Prototype]]
property must have been in a hurry... So, [[Prototype]]
is present on the newly created veggies object, and we did not have to do anything. In fact, every object has [[Prototype]]
property.
In this example, it seems to link to something called Object
, which is a constructor function. But it does not directly point to the Object constructor itself. In fact, it’s pointing to the prototype property (remember, functions are objects. Broken record, I know, two times). We’ll this more about this in a minute, depending on your reading speed.
All functions are objects, all objects have a
[[Prototype]]
property, so all functions have a[[Prototype]]
property — Introduction to formal logic - Some ancient greek philosopher
We now know how the links of the prototypal chain work, the “bracket” prototype present on every object points to another object, or null
:
If we want to work with old-fashioned prototypal inheritance, we can use Object.create
to set explicitly the prototype of a newly created object. We pass an object to Object.create
and it will be the object “bracket” prototype points to. So far so good.
But what happens in the other pattern, the constructor pattern where we do not specify which object the newly created object should inherit from? It seems to me that JavaScript is doing things that we don’t see. It is lying to us. I knew it. How are these prototypes set in our backs when using constructors?
Enters the prototype property prototype
, or the constructor function prototype property. You know, by opposition to the object prototype property [[Prototype]]
which happens also to be a property on constructors. I prefer to call it “dot” prototype to make things clear. Or more complicated, who knows?
III — What and Where is the "dot” prototype
?
Every function has a “dot” prototype
property, and this one is not hidden. Whether it is used as a constructor, or not. Why? Because… well, functions are objects (three times) and we can put a prototype property on it. So, someone decided to put one on functions. You know, just in case, if we want to use this function as a constructor. So, let’s take a second iteration at that. Every function has a “dot” prototype
property. Why? Well, because…
Objects created from a function with the new
keyword will have their prototype set to the prototype
property of the constructor function that created the object.
So there must be a prototype
property on the constructor function. Let’s check it:
> function Constr() {}
> Constr.prototype;
{constructor: ƒ}
constructor: ƒ Constr()
[[Prototype]]: Object
There is a dotprototype
property on the Constr
function. Interesting. Our .prototype
value is an object made of a `constructor` property and the not-so-hidden property [[Prototype]]
.
Now, we have all we need to complete our little drawing and see exactly what happens when we create plain objects using the 3 object creation patterns:
User-defined constructor function and prototypes
“So what happens when we use a constructor function that we created?”
I really like your questions, you know. Glad you asked. Now you are all grown-up and understand how prototypes work in JavaScript, we are going to see what happens when we create an object from a constructor function that we defined. Let’s create an object lao
with a constructor User
:
> function User(name) {
> this.name = name;
> }> let lao = new User('Lao');
This is what happened from a prototype point of view:
How can 4 lines of code make such a mess? Again, javaScript is working behind our backs. It looks complicated but please note that:
- All functions have a “dot” prototype property (three times)
- All objects have a “bracket” prototype (three times), and that, of course, includes functions
- Functions “bracket” prototype all points to
Function.prototype
, which “bracket” prototype then points toObject.prototype
, allowing functions to inherit from both function level methods (with methods likecall
,apply
, andbind
) and object-level methods (hasOwnProperty
, ...). Remember? Functions are objects (four times), so it makes sense that they inherit from both. - After one or 2 links, all bracket prototypes point to the Object.prototype which bracket prototype points to
null
, ending the prototypal chain
Now, you know almost everything there is to know about prototypes as the last “prototype” is dunder proto, which you should not use. This next chapter doesn’t exist, it never did.
IV — What and Where is __proto__?
Dunder proto is sometimes used and it can be useful to understand how it works since it is a static method. Let’s try to locate it without the documentation. Does it exist on the object itself?
> let obj = {};
undefined
> obj.hasOwnProperty("__proto__");
false
It could be a hidden property but it seems that it’s somewhere else… Maybe the prototype?
> let objPrototype = Object.getPrototypeOf(obj);
> objPrototype.hasOwnProperty("__proto__");
true
We found it! It’s located on the created object prototype (.prototype
in that case).
> objPrototype;
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
__proto__: (...)
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
__proto__
is actually on the prototype property of the Object constructor!!! And it’s actually not a property but an ensemble of a getter and a setter functions that the object inherits from the Object
constructor 🤯.
__proto__
is an accessor property, it gets or sets the value of the [[Prototype]]
of the object it is called on. For example, if we use __proto__
as below, we call a method that gets the value of the bracket property of obj
:
> obj.__proto__
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
The object returned looks awfully similar to the object returned above. Are they the same object?
> Object.prototype === objPrototype;
true
They are, and now, we can complete our little drawing:
So, actually, when we type obj.__proto__
, JavaScript will look on the object if it finds the property. As it doesn’t find it, it then looks on the prototype (the value referenced by the “bracket” prototype), here Object.prototype
. And it finds it. Then, the way we use the dunder proto tells JavaScript if we want to get the value of the prototype or to set the value of the prototype.
But forget that you have seen it. This part of this article doesn’t exist, I will deny ever writing it.
A summary
Objects inherit from another object through the [[prototype]]
hidden property located on every object. Except when we set the [[prototype]]
to null
. When a prototype is null
, this is the end of the prototypal chain. All functions have a “dot” prototype property. This “dot” prototype is used to give a prototype to the object created when functions are used as constructors. We can use the dunder proto methods to set or get this [[prototype]]
. This dunder proto methods are located on the Object constructor and are inherited by all objects. You should not use it.