Sunday, November 16, 2008

Object Orientated JavaScript - Part 1

So its that time again when a project is drawing to a close and I'm beginning to clean up the codebase and make sure everything is in order.

Usually this is straight forward and it's the usual steps of: review code, refactor, bug fix, test, document etc. This process can take days, or depending on the amount of bugs and changed requested by the client, weeks or months.

The plan with this project from the very beginning was to build up a code base of varying libraries which would work together as a loose framework which could then be refactored into a more solid Framework.

This week I began the cleaning up process. I started with a review of the codebase that had been growing since a little over one month ago now. Some of this code came from an earlier iPhone Project which used a slapped togeather framework named 'iCore', while other code was borrowed from other libraries.

So I started with all the Object Orientation libs. We had been using a mixture of JavaScriptMVC's new classing engine with a few modifications and a Package and Namespace library.

The classing engine is the critical component in the entire framework and even application. That, mixed with packages and namespaces have enabled complete modularization of the codebase... But I felt it wasn't enough. The classing engine, although being a solid implementation (based off of John Resig's Simple Javascript Inheritance), wasn't enabling a lot of features that many experienced programmers have come to expect from any object orientated language.

In addition to this, it has a critical flaw in its design. I can't speak for all classing engine implementations out there, but from my experience JavaScriptMVC (and Simple Javascript Inheritance) and Prototype all suffer from this flaw. The flaw itself is fundemental to a 'class' truely being a class. What am I talking about? I'm talking about what happens when an instance of a class is created.

Now I'm quite sure that when a programmer uses the keyword new in their code, they expect to get something new. However this apparantly isn't the case with these other implementations. Allow me to demonstrate.

To demonstrate I'll use JavaScriptMVC as the classing engine.

Create class Foo
var Foo=Class.extend
(
{
baz:
{
item1: 'A.1',
item2: 'A.2',
},
init: function(){}
}
);
Initiate Foo twice and change a property of baz
var foo =new Foo();
var foo2=new Foo();
foo.baz.item1='B.1';
If we were to now inspect both foo and foo2, we would expect to see foo.baz.item1 equal B.1 and foo2.baz.item1 equal A.1. However this is unfortunately not the case. Since foo.baz.item1 was changed every instance, including the base class' (Foo.prototype.baz.item1) have been changed.

Clearly this is a major problem. new is not creating a completely new instance of the class. However, it is making a partially new instance. Basically, any object in that has any depth (ie. children), won't be correctly initiated as a new object within the class.

The problem is fundamentally to do with how JavaScript deals with passing things around. JavaScript passes primitive values, such as strings and numbers, by value while other more complex values, such as functions and objects, by reference. And that right there is the problem. Complex types are being pushed around by reference and there is no clear way to actually make a copy. There is a way to get around this however and so I decided to write my own classing engine with one of the goals being to beat this problem.

So a couple days past and I finished the new classing engine. Its a rock solid Object Orientated implementation. Its simple to use and as far as I can tell, surpasses most other JavaScript classing engines available today.

Core Features:
  • Namespacing
  • Extending
  • Interfaces
  • Traits
Other Features:
  • Simple to use
  • Behavioral Traits
  • True Object Orientated Classing (new means new!)
Those are the features that make up the new classing engine. I'm going to talk a bit about these features and show some examples along the way.

Firstly, I'm going to explain how I fixed the critical issue mentioned above. The way in which this can be fixed is to deep copy the object. Or in this case, the entire prototype. This is done by using a set of pre-existing functions that I had written, namely 'extend' and 'clone'.

These perform deep copying by looping over each property of a given object and recursively calling itself if it encounters another object. And this here is the secret. If you simply copy something by doing var x=y, then it's going to create a reference, however if you go deep and individually copy each property of y, then it'll create a copy. Lets look at an example.

var foo=
{
item1: 'A.1',
item2: 'A.2',
item3: 'A.3'
}
//Create a copy by reference.
var bar=foo;
//Create a copy by value.
var baz={};
for (item in foo)
{
baz[item]=foo[item];
}

Clearly there is a little bit more overhead with this approach. Unfortunately that is the cost of doing Object Orientated programming in a procedural language. There simply is no other way to make this work correctly.

Now depending on the implementation, the actual fix to make the class engine do this may be different. My implementation uses a function to act as the internal constructor (when you instantiate with new) and then calls the init method which acts as the user-land constructor. Before this is initiated, some stuff happens (namely the behaviors of Traits are handled).

The trick is to clone the prototype, make the required changes to the active instance (this), then restore the original classes prototype with the cloned prototype. This will restore the class back to its original form without breaking any existing or the currently initiating instance(s) of the class. Example:

baseClass=function()
{
//The first and most important thing to do is to make a clone of the class prototype.
var prototype=Object.clone(baseClass.prototype);

//Do stuff...

//Now restore the class prototype back to its original form.
baseClass.prototype=prototype;
//Finally, initiate the class.
this.init.apply(this,args);
};
I hope other JavaScript library maintainers see this and fix up their code. Its a simple fix which would eliminate a lot of head scratching by developers using the library.

I'm going to stop typing now because this is getting pretty long and I'd rather break it up. I'll do another entry soon and go into some details regarding other features in this classing engine.

No comments: