Saturday, September 17, 2011

javascript functions untangled

After a week-long odyssey of trying to understand how JS functions connect to other objects, namely where their prototype, __proto__ and constructor properties point to, I think I've finally achieved enlightenment.

Most JS tutorials and books are concerned with how functions connect to objects created by them, not with how they connect with objects that are created before them.

I am really amazed that I haven't been able to find a simple chart (e.g. a UML diagram) of this anywhere. I've found idealized charts that leave out a lot of the details, but I haven't found anything resembling what I've put together, below.

If you know of a better chart, please link to it.

If you find an error in mine, please let me know. I am not a JS expert, though after researching this, I am inching closer to being one.

Note: the __proto__ property isn't an official part of the ECMAScript spec. It was invented by Mozilla in order to give you access to a hidden property of objects -- a property that, according to the spec, you're not supposed to be able to access. (In the spec, that property is called [[prototype]]. Mozilla exposes it as __proto__).

Notes On the Chart

===== f.constructor =====

A constructor (not the constructor property of f, but constructors in general) is an function used to create objects. In JS, you can create a new object in a variety of ways. If you're creating a new function (in JS, functions are objects that have the extra ability of being callable), you can do so this way...

var f = function() {};

or this way

var f = new Function();

The former way is the standard, but it basically evokes the latter way behind the scenes. So Function() (with a capital F) is a constructor -- it's a function that creates other objects (when used in conjunction with the new operator). Specifically, the Function() constructor creates functions (which are objects).

This is why, if you look at the chart, above, the f object (a function) has a constructor property that points to Function(). Function was f's constructor. Function was the constructor function used to create f. Objects in JS (such as f) have constructor properties that point to the function that was used to create them.

===== f.__proto__ =====

(I'm going to write as if __proto__ was a standard property of JS, but see my note above. It basically IS a standard property, but the spec defines it as hidden -- inaccessible -- whereas Mozilla allows you to see it.)

Any object's __proto__ property points to its parent object.

If I have an object called x and I try to access its color property (x.color), what happens if x doesn't have one? Well, before JS starts shouting "Error! Error! Error!" it first checks to see if there's an x.__proto__.color property.

In other words, if x doesn't have color but x's parent has it, JS acts as if x DOES have a color property. When you ask for the value of an object's property, you are really saying "Give it to me if you or any of your ancestors have it."

JS will keep walking up the chain -- x.__proto__.__proto__.__proto__ ... -- until it finds color or until it gets to Object.prototype (the trunk of the tree) and finds that IT doesn't have color. It will THEN say "Error! Error! Error!" (actually, it will just tell you that x.color is undefined).

In a language like java or or c++, x.__proto__.__proto__ would be written as x.parent.parent.

In JS, if you create a simple object...

var x = {};


var x = new Object();

That object's __proto__ is Object.prototype, the trunk of the tree. It gives x access to basic methods like toString(). When you type x.toString(), JS goes "Hey! x doesn't have a toString() method. Error! ... Oh, wait.... I should check x's __proto__... Let's see, x's __proto__ is Object.prototype and that has a toString() method. So I shouldn't have said 'Error.' My bad."

f.__proto__ points to Function.prototype. This is because functions need access to some things besides basic Object methods like toString(). For instance, they need access to call(), apply() and a property called arguments.

I didn't include this on my chart, but f.__proto__.__proto__ === Object.prototype. So functions inherit from Function.prototype which inherits from Object.prototype. This allows me to use (from Function.prototype) and f.toString() (from Object.prototype).

===== f.prototype =====

One of the most confusing aspects of JS is the distinction between __proto__ and prototype.

__proto__, as discussed above, is the parent of an object. Whereas prototype is the object that constructor functions will inject into the __proto__'s of the objects they create. (If that doesn't make sense, keep reading._

Note that if you type this...

var x = {}; //Or var x = new Object();

... and examine the result in Firebug or Chrome's JS console, you'll see that x has a __proto__ but no prototype. That's because only functions have prototypes, because only functions can be used as constructors, and only constructors inject values into the __proto__ properties of objects they create.

It's VERY confusing to say that only functions have prototypes, because -- if we use the word "prototype" in a different way, as I've done many times, above -- all JS objects have prototypes, meaning that they all have parents.

But remember that an object's parent is stored in its __proto__ property, NOT in its prototype property (most objects, not being functions, don't have a prototype property).

Even a function's partent isn't stored in its prototype property. A function's parent is stored in its __proto__ property, as is the case with all other objects. So it's not the case that only functions have prototypes (parents); it's the case that only functions have PROPERTIES called "prototype," and they DON'T store a pointer to their parents in those properties.

I wish JS (or mozilla) would rename __proto__ to parent and rename prototype to parentOfChilrenMadeFromMe.

So, think of f.prototype as f.parentOfChidrenMadeByMe.

If you type this...

var x = new f();

... this happens behind the scenes:

x.parent = f.parentOfChildrenMadeByMe

Well, actually, that's...

x.__proto__ === f.prototype

So what is contained in f.prototype (what value does it inject into the __proto__ properties of the objects it creates)? As my chart suggests, f.prototype is an object that contains __proto__ and constructor properties.

===== f.prototype.constructor =====

This points back to f. Why? Remember, f.prototype is a "gift" that f (a constructor function) gives to all the object it constructs. So, if x is one of those constructed objects...

x.__proto__ === f.prototype

So if f.prototype.constructor is f, then f.prototype.constructor is f, which means x.__proto__.constructor is f, too.

Remember, __proto__ is "parent," and you can access a parent's properties through its child. So, since x is a child of its own __proto__ (its own parent), that means that x.constructor is ALSO f. Or rather, when you ask for x.constructor, JS doesn't say "Error! Error!" It first checks to see if x.__proto__ has a property called constructor, and when it finds that it does, JS returns its value as if it's stored by x.constructor.

So by setting f.prototype.constructor to f, JS has found a sneaky way for x (or any object f constructs) to know who constructed it.

===== f.prototype.__proto__ =====

So who is the parent of f.parentOfChildrenMadeByMe? In other words, if I type...

var x = new f();

... x's parent (its __proto__) is f.prototype. (If you don't understand why, read the above section called "f.prototype."

But what is x's parent's partent? It's f.prototype.__proto_, and THAT is Object.prototype, the trunk of the tree -- the object that gives all its children core methods like toString().

How Do I Know All This?

I wish I could site sources, but I cobbled this together via many hours of googling, and by combining information from dozens of sites and books, and I didn't save all of my steps. But I tested my understanding with this script, checking the output in Chrome's JS console.


var f = function() {};
console.log( "f.__proto__ === Function.prototype --> ", f.__proto__ === Function.prototype ); //true
console.log( "Function.prototype --> ",Function.prototype ); //displayed by Chrome's console as Function Empty() {}
console.log( "f.__proto__.__proto__ === Object.prototype --> ", f.__proto__.__proto__ === Object.prototype ); //true
console.log( "f.constructor --> ", f.constructor ); //function Function() { [native code] }, e.g. Function()
console.log( "f.prototype --> ", f.prototype ); // Object
console.log( "f.prototype.__proto__ === Object.prototype --> ", f.prototype.__proto__ === Object.prototype ); //true
console.log( "f.prototype.constructor === f --> ", f.prototype.constructor === f ); //true
console.log( "f.__proto__ === f.prototype.___proto___", f.__proto__ === f.prototype.___proto___ ); //true

var x = new f();
console.log( "x.__proto__ === f.prototype --> ", x.__proto__ === f.prototype ); //true
console.log( "x.__proto__.__proto__ === f.prototype.__proto__ === Object.prototype --> ", x.__proto__.__proto__ === f.prototype.__proto__ ); //true


If you're trying to understand JS, I urge you to play with the script, look at my chart, and read through this post until you "get it." The pay-off for me has been immense.

1 comment:

Anonymous said...

The JavaScript communities should remove you because you are a psychopath who bullies innocent people.