Friday, 30 November 2012

prototype, [[Prototype]], inheritance chains, contructors...

The JS101 section in dailyjs is always worth a reading. However much I think I know something I always find here some nuance on the subject at hand that I hadn't realized of and makes me mull over it. Reading their entry about prototypes was not different.

Prototypes were a confusing matter for me for a long time. While the idea of a prototype chain to look for methods and properties is simple, powerful and beautiful, it took me time to realize that the prototype chain (inheritance chain) is based on the [[Prototype]] property (accessible in many environments through the non standard __proto__ property), and that the prototype property that we find only in Function objects, is not part of that chain (but is used for setting it up...) Bear in mind then, that functions have both a prototype (pointing to an object which constructor property points back to the function) and a [[Prototype]] that sets their "inheritance chain". This prototype chain for functions contains two Objects: Function.prototype and Object.prototype.
(function(){}).__proto__.__proto__ === Object.prototype;
I like the name they use in the article for referring to [[Prototype]]: internal prototype, to distinguish it from prototype.

As I said, __proto__ is not standard, but if we want to get hold of the [[Prototype]] object, we can do it using ES5's Object.getPrototypeOf

One very important point is that Object.prototype.__proto__ is null, that's the way the interpreter can find the end of the prototype chain. This used to be the only object without a prototype chain, but with the advent of Object.create(), we're now allowed to create other objects with their [[Prototype]] set to null. I already mentioned an odd JS behavior related to this in this previous post

There's one interesting point mentioned in the daily.js article when they talk about Object.create as the only way to obtain an object without a prototype chain (that is, [[Prototype]] points to null). We could think of achieving the same by setting the constructorFunction.prototype to null, but contrary to what we could expect, that does not give us a null [[Prototype]] when creating an instance, but makes it point to Object.prototype. I guess it's one of those corner cases explained somewhere in the ECMAScript reference

To make sure we understand how prototype and [[Prototype]] work, we can do some checks like these (all of them are true):

given: function f1(){};
  • f1.prototype.__proto__ === Object.prototype;
    (the prototype of a function is just a normal object, so it's __proto__ is Object.prototype)
  • Function.prototype.__proto__ === Object.prototype;
  • f1.__proto__ === Function.prototype;
    (notice that functions are instances of Function)

Anyway, there are some cases that can't be inferred, and work that way just cause the standard says so, these are some of them:

  • Function is an odd object, it's an instance of Object, but does get its __proto__set to an empty function, so:
    Function.__proto__ === Object.prototype; is false
    Moreover, I found somewhere this interesting additional info:
    Function.prototype is a function, that returns always undefined and can accept any number of arguments, but why?. Maybe just for consistency, every built-in constructor's prototype is like that, Number.prototype is a Number object, Array.prototype is an Array object, RegExp.prototype is a RegExp object, and so on...
  • Indeed, we should not care much about Object.__proto__ and Function.__proto__, as I can't think of them being used in any inheritance chain
This question in SO has additional interesting notes about the above.

Talking about prototype chains, we should mention the instanceof operator:
ob instanceof MyFunction;
that checks if the given constructor function is in the the prototype chain of the given object (by walking up the prototype chain and comparing the objects it finds with MyFunction.prototype). We can achieve just the same by using Object.isPrototypeOf. We would write:
MyFunction.prototype.isPrototypeOf(ob);

The best way to complete this post is to add this extraordinary diagram that so perfectly explains all this and that I found here

and to summarize below (also taken from there) what happens when an object is created by a call to a constructor (i.e. var f = new Foo();)

The interpreter creates a new, blank object.
The interpreter sets the new object's underlying prototype to be Foo.prototype.
The interpreter calls Foo in a way that makes this refer to the new object.
When Foo completes, if Foo doesn't return a value (or doesn't return an object), the result of the new expression is the object created in step 1. (If Foo returns an object, that object is used instead; that's an advanced thing most people don't have to do.)

No comments:

Post a Comment