Wednesday, August 12, 2009

Would the Real Prototype Please Stand Up?

There are a ton of examples out there of how to Prototype with Javascript. The examples I have seen appear too complex, verbose or just plain wrong. I started OO in Java and found the switch to Javascript incredibly difficult because of some bad descriptions of what was a Prototype was actually used for.

Constructors
Prototypes are NOT blueprints of an object. They are just links to objects you want to inherit member variables from. But it is not any use knowing what a Prototype is, unless you first know what a Constructor is. So lets jump right in and define a Constructor.


// A constructor
God = function God(){
this.myName = 'YHWH';
this.thinks = true;
}

A Constructor is simply a function. The Constructor function has member variables. In this case the member variables are 'myName' and 'thinks'. Using 'new' Now we are able to make copies of Gods.
// Create some Gods
Father = new God;
Son = new God;
Spirit = new God;

Thus we have created 3 new Gods. Each of these Gods has their own name and their own thoughts. Important: they have NOT 'inherited' the member variables from the God constructor. The member varibles are new, as each new instance of God is... 'new'. IE: God does not inherit anything from God, when God is at the top of the chain.

Prototypes
Once you understand that a Constructor is just a function used to create a 'new' instance and set new member variables within that instance... you will find it easy to comprehend what a Prototype is.

Prototypes are not 'created', that's the Constructor's job. Prototypes are linked. Lets say that God wanted to create Man. First God would need a constructor function by which to make the man. So lets code one...
 // Define Man's Constructor
Man = function Man( name ){
this.myName = name;
this.dna = 'ACGT';
}

Now you can create a new Man like this...
 Adam = new Man( 'Adam' );

Here we set the name-space 'Adam' to store a new instance of Man. When we invoke 'new Man', we want to pass Adam his name, so: 'new Man( 'adam' )'. Thus the Man construtor is run and Adam's member variables are set. But in this case... we have created a dead man (Adam can not think).

God can think but Adam can't. Wouldn't it be nice if Adam could inherit his thinking ability from his Creator? It would, and he can. This is where Prototype is used. Let's append the Man object...
 // Define Man's Constructor
Man = function Man( name ){
this.myName = name;
this.dna = 'ACGT';
}
// Define Man's Prototype
Man.prototype = new God;

Ahha! Now when ever a new Man is created, the new man gains the ability to think from his creator. Pointing Man's prototype to 'new God' creates a link that copies the member variables from the God constructor to the new instance of Man.

Prototypes are Links

Taking things to their logical conclusion: now that Man can think, he can realize that he is alone and needs a new Woman. Now we define the constructor function for Woman.
 Woman = function Woman( name ){
this.myName = name;
this.womb = true;
}
Woman.prototype = new Man;

Notice that Woman's prototype is Man. This means that any new Woman will inherit the Man constructor's member variables. Thus the Woman inherits DNA from the Man. Woman additionally gains a member variable called 'womb', which Man and God do not have.

So here is where the beauty really comes in: Woman can think, even though Man has no thinking ability in his constructor. This is because prototypes are links. And what do links form? Chains!

As you chained Woman to Man, every time you create a new instance of Woman, she inherits her member variables from new Man, who inherits his member variables from new God. Thus we have a chain of inheritance, and anything you set at the top level will filter down to the bottom unless you explicitly stop or redirect the flow of information.

All the Code
Lets put it all together and see what we get.
   // God constructor
God = function God(){
this.myName = 'YHWH';
this.thinks = true;
}

// Man constructor
Man = function Man( name ){
this.myName = name;
this.dna = 'ACGT';
}
// Man's Prototype is God's constructor
Man.prototype = new God;

//Woman constructor
Woman = function Woman( name ){
this.womb = true;
}

// Woman's Prototype is Mans's constructor & Man's prototype
Woman.prototype = new Man;

// Run the Man constructor against the new Woman object ( so Woman gets a name )
Woman.prototype.constructor = Man;

// A method to see inside the object
function inspect( being ){
var arr = {};
for( var i in being ){
arr[ i ] = being[ i ];
}
console.log( being.name || being.myName, arr );
}

/* Make an Adam & Eve from Woman & Man constructors...
...inheriting members from their prototypes links */
Adam = new Man( 'Adam' );
( Eve = new Woman ).constructor( 'Eve' );

// Console log for clarity
inspect( God );
inspect( Man );
inspect( Woman );
inspect( Adam );
inspect( Eve );

// constructor properties get added first
// then prototyped properties get inherited second

Results:

God Object prototype=Object
Man Object prototype=Object
Woman Object prototype=Object
Adam Object myName=Adam dna=ACGT thinks=true
Eve Object myName=Eve womb=true dna=ACGT thinks=true

Conclusion:
Pretty much everyone knows the account of Genesis, so we all know the fundamental differences between God, Man and Woman. Hopefully this has helped make Prototypes easier to understand for someone out there.

Prototypes are really very simple, but the terminology can be pretty confusing when you look at all the examples out there, especially if you are just beginning to learn Object Oriented patterns. I started learning OO in Java which was much easier to understand than JS. But now that I have been using Prototypal behavior in Javascript for a while, I have to say... I prefer it.

7 comments:

  1. My own 'a-ha' moment with prototypes came when I carefully read the sentence from the mozdev tutorial: there are no classes in javascript, only objects. ~zeroaltitude

    ReplyDelete
  2. I never really understood prototypes in JS until I read this, cheers :)

    ReplyDelete
  3. remove static properties from the constructor, put them in the prototype, never forget to reassign the constructor property, and finally, your Woman has an undefined property name by default.

    This is prototype, and nothing else:
    function A(){};
    function B(){};
    (B.prototype = new A).constructor = B;

    the constructor should be called, when necessary, to initialize inherited instances as well.

    As example, in Woman makes sense, if you've got JavaScript, call the parent rather than assign the name (redundant, name is assigned in Man constructor).

    function Woman(name){Man.call(this, name);};
    (Woman.prototype = new Man).constructor = Woman;
    Woman.prototype.womb = true;

    Only if modified, womb will become a privileged public property rather than a linked one. Same is for those linked via new Man.

    P.S. your area has something extremely NOT user friendly. Regards

    ReplyDelete
  4. for "this is prototype and nothing else" I meant that prototype IS simple, and that is how it works (link between objects). You are right when you say there are no classes, just objects ;-)

    ReplyDelete
  5. @Andrea Giammarchi - "P.S. your area has something extremely NOT user friendly. Regards" - what do you mean?

    ReplyDelete
  6. @Andrea Giammarchi - Ah yes, see what you mean about the constructor now, good tip!

    ReplyDelete
  7. That was not a tip, that is how things should be in an emulated classical inheritance environment. Prototypal inheritance is different, and I'll never get why people from other "classical" OOP concepts would like to make JavaScript "like that".
    In any case, if this it the post about, as you use super or parent in Java or whatever programming language, you should do the same with JavaScript 'cause classes do not exist but their "abstract concept" are reproducible without effort.
    parent.apply(this, arguments) by default, the rest only if necessary.
    The constructor is extremely important 'cause could save our journey trying to understand what exactly is a variable, and what is not.

    Finally, about this textarea, for some reason befre I was not able to use my keypad, my arrows, copy and paste, and something else.
    This is probably not your fault at all so apologies for the comment.

    Best Regards

    ReplyDelete