Prologue
This post briefly explains the differences (and connections) between prototype
, [[prototype]]
, and __prototype__
.
I was diving into the topic of JavaScript’s inheritance and the prototype chain on Mozilla’s website, where you can usually find in-depth meanwhile reasonably-scoped documentations and explanations of JavaScript standards (#1 place for me to consult whenever in doubt with JS). However, a demo code block on the page really confused me with the way JavaScipt looks up a property on an object (instance of an Object
), and I ended up looking through the details of “prototype” in JavaScript’s universe.
Problem Encountered!
As per my previous understanding, when looking for a property on an object, JavaScript first checks if that property exists in that object’s “own properties” (analagously someObject.hasOwnProperty()
), and then falls back to its prototype’s property
s, and so on down to Object
‘s prototype (notice the big O), which is null
.
Then I came across the following code block from the aforementioned article:
1 | function doSomething(){} |
According to the article (and my browser’s console), the above results in the following:
1 | doSomeInstancing.prop: some value |
What specifically concerned me was that doSomething.foo
produced undefined
after defining foo
on doSomething
‘s prototype, but doSomeInstancing.foo
returns bar
! (on line 2 and line 4 of the first code block) According to what I have mentioned in the beginning of this section, I thought that JavaScript would be able to find foo
in doSomething
‘s prototype foo
. I tried it on my browser, and Mozilla was right:
WTF? Since both a
and b
had foo
on their “prototype
“ (I had assumed), why was a.foo
undefined
? The only difference I could think of was that a
was a function
(Function
object), while b
was an inherited instance created from function
a
, so that b
should inherit the “prototype
“ properties of a
.
A Closer Look on “prototype”
After (quite) some efforts (thanks to StackOverflow users and Mozilla), I discovered that it was JavaScript’s overlapping (overloading?) literature of the word “prototype” that caused the confusion. In a word, every JavaScript object (which is an instance of Object
) has an innate prototype property, denoted in documentations by [[prototype]]
(notice the double square brackets), but it is hidden, i.e., the JavaScript standard (ECMAScript) has not specified a way for directly accessing it as you normally would using the dot (.
) notation (calling someObject.[[prototype]]
would result in syntax error in any browser). If you insists on doing so, this built-in [[prototype]]
of every javascript object can be accessed by passing the latter to Object.getPrototypeOf()
.
As a matter of fact, however, to simply the calling process, most browsers have implemented a pseudo-property name __proto__
, which points to this [[prototype]]
, so the following statement is always true for any browser that has implemented __proto__
(provided that someObject
is not null
or undefined
):
1 | someObject.__proto__ === Object.getPrototypeOf(someObject); // true |
It has thus become common to use __proto__
to denote the [[prototype]]
of a JavaScript object.
Now here’s the confusing part (especially for people unfamiliar with JavaScript):
Despite all the above, JavaScript provides a prototype
property for its Function
objects! Following the chained prototype interitance logic, this Function.prototype
is used for adding properties that you want a function (as a constructor) to default its derived instances with. So the following is factful:
1 | let aFunction = function(){}; |
Notably, foo
is not a property of aFunction
(or any of its __proto__
chain members), so calling foo
on aFunction
will result in undefined
. However, foo
is a property of bInstance
‘s __proto__
(since bInstance
is instantiated (created) by calling new aFunction
). To clarify this, the following statement is always true:
1 | aFunction.prototype === bInstance.__proto__; // true |
and changing the value of foo
on bInstance.__proto__
will be reflected on the value of aFunction.prototype.foo
, too:1
2bInstance.__proto__.foo = "rab";
console.log(aFunction.prototype.foo); // "rab"
Recap
Eventually, it was made clear why the code block I had read about should behave in that particular way. The important thing here is that for any object:
- its
__proto__
is always equivalent to its[[property]]
(they point to the same address) - if this object happens to be a
Function
object, then neither itsproto
nor[[prototype]]
is euqal to itsprototype
property (1. is still true) - for a Function object
aFunction
and its instance objectbInstance
,aFunction.prototype
is equivalent tobInstance.__proto__
(they point to the same address)
What a mess.