prototype vs [[prototype]] vs __proto__ in JavaScript

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 propertys, 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
2
3
4
5
6
7
8
9
10
function doSomething(){}
doSomething.prototype.foo = "bar";
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value";
console.log("doSomeInstancing.prop: " + doSomeInstancing.prop);
console.log("doSomeInstancing.foo: " + doSomeInstancing.foo);
console.log("doSomething.prop: " + doSomething.prop);
console.log("doSomething.foo: " + doSomething.foo);
console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);
console.log("doSomething.prototype.foo: " + doSomething.prototype.foo);

According to the article (and my browser’s console), the above results in the following:

1
2
3
4
5
6
doSomeInstancing.prop:      some value
doSomeInstancing.foo: bar
doSomething.prop: undefined
doSomething.foo: undefined
doSomething.prototype.prop: undefined
doSomething.prototype.foo: bar

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
2
3
4
5
let aFunction = function(){};
aFunction.prototype.foo = "bar";

bInstance = new aFunction();
console.log(bInstance.foo === "bar"); // true

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
2
bInstance.__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:

  1. its __proto__ is always equivalent to its [[property]] (they point to the same address)
  2. if this object happens to be a Function object, then neither its proto nor [[prototype]] is euqal to its prototype property (1. is still true)
  3. for a Function object aFunction and its instance object bInstance, aFunction.prototype is equivalent to bInstance.__proto__ (they point to the same address)

What a mess.