Showing all posts tagged "Javascript"
Object Inheritance with Javascript
Recently I've been spending a lot of my time experimenting with and writing web-applications powered by a fair bit of Javascript. I've toyed with a couple of the new big hitters in the world of Javascript; namely Prototype and MochiKit. If you haven't heard of either library, I encourage you to take a peak and explore them both. They provide a large set of useful functions and features that will help to save you time when writing your own Javascript. I'm not about to discuss which one is better than the other, nor am I going to do a feature comparison between both libraries. There are plenty of articles and blog posts that already do just that. Instead I am going to focus on something else; something that neither of those two libraries really help with. I am going to focus on writing Object-Oriented Javascript.
There are a lot of pitfalls and not so attractive code that is needed in order to accomplish certain OO tasks in Javascript. I've written a little library that I think helps to simplify all this. However, before I get into the details of my own work, I think a little background is in order. Of course, you could skip right down to it if you are already familiar with the pitfalls of writing OO in Javascript.
A little background
As many readers may already know Javascript is a prototype-based language3. It is possible to define your own objects, however the syntax for doing so is somewhat clumsy. I won't go into all the details on how to create objects since you can read them for yourself in the Core Javascript Guide, but here's a brief rundown.
Traditionally the following code would define a constructor function for our Employee object.
Employee = function() {
<a href="http://this.name">this.name</a> = '';
this.dept = "general";
};
Methods can be added to our Employee object with the following:
Employee.prototype.doWork = function() {
return "Employee is working...";
};
Employee.prototype.takeBreak = function() {
return "Employee is slacking off...";
};
The above code is quite verbose and cumbersome. Alternatively, the following syntax will achieve the same effect.
Employee.prototype = {
doWork: function() {
return "Employee is working...";
},
takeBreak: function() {
return "Employee is slacking off...";
}
};
This is a bit cleaner, however defining your object still requires two independent chunks of code. Also, the situation just gets even worse when you try and create a hierarchy of objects.
Manager = function() {
this.reports = [];
};
Manager.prototype = new Employee();
Here we have defined Manager to be a subclass of Employee. However, now your only option to add methods to our Manager object is to use the traditional way.
Manager.prototype.manage = function() {
return "Manager is now micro-managing...";
};
The following will not work because we would loose our object hierarchy.
Manager.prototype = {
manage: function() {
return "Manager is now micro-managing...";
}
};
If you are using either of the Prototype or MochiKit libraries you can however cheat a little here and use a handy feature that is present in both libraries.
Using Prototype:
Manager.prototype = Object.extend(new Employee(), {
manage: function() {
return "Manager is now micro-managing...";
}
});
Using MochiKit:
Manager.prototype = MochiKit.Base.update(new Employee(), {
manage: function() {
return "Manager is now micro-managing...";
}
});
Object.extend(..)
and MochiKit.Base.update(..)
both perform the same task of mutating and returning the first parameter replacing all of it's key:value pairs with those from the second parameter. This allows you to create an object hierarchy and keep using the cleaner syntax for defining your Objects methods.
All of this works great until you want to call a method on a parent class. There is no nice way of doing this natively in Javascript. Instead, you have use some rather explicit and gross looking code the uses either Function.apply or Function.call.
Manager.prototype.doWork = function() {
return Employee.prototype.doWork.call(this) +
"managing the rest of the employees.";
};
This bit of code isn't very nice for a number of reasons. The one reason I dislike the most about this bit of code is that it doesn't make life easy if you ever come back and alter your work. Lets say your hierarchy changes and Managers are no longer Employees, instead they are Owners. Now you have to go searching through all your code looking for all the cases where you calledEmployee.prototype.someFunc.call(..)
.
There isn't really a nice way around this without adding some additional features to the language. I spent some time the other evening trying to piece together a nice extension that will make the syntax for writing Objects cleaner and simpler, while still playing nicely with those big hitting libraries. The complete JS is available for download at the bottom of this post. The next few examples will demonstrate how to use my little extension.
The Good Stuff
I've created a Class object that mimics the Class object found in the Prototype library. If you already use Prototype, your old code should continue to work without any side effects. At the core of my extension is the Class.create()
method.
In order to create a new Object you can use the following syntax (if you use the Prototype library already, then this will be familiar).
Employee = Class.create();
Employee.prototype = {
initialize: function() {
<a href="http://this.name">this.name</a> = "";
this.dept = "general";
},
doWork: function() {
return "Employee is working...";
},
takeBreak: function() {
return "Employee is slacking off...";
}
};
So what is this doing? In this case Class.create()
creates a constructor function for us. The created constructor will call the initialize method on Object creation. So in essence we can treat initialize as our constructor. So far this functionally the same as the Class.create()
method found in the Prototype Library, however thats not all I have done. I took this idea one step further and combined both the constructor creation and the prototype definition in one convenient call.
Employee = Class.create({
initialize: function() {
<a href="http://this.name">this.name</a> = "";
this.dept = "general";
},
doWork: function() {
return "Employee is working...";
},
takeBreak: function() {
return "Employee is slacking off...";
}
});
The above code will create the Employee object we created earlier. You no longer have to separate your Object definition into two pieces. Its all done at the same time.
But wait, there's more! I took it another step further. What happens when you want to create an object hierarchy?Class.create() can do that too!
Manager = Class.create(Employee, {
manage: function() {
return "Manager is now micro-managing...";
}
});
So now using a very simple syntax we've created our Manager Object, that inherits functionality from Employee and we've defined the manage method that only Managers have.
Well what about that problem of calling methods defined in the parent class? Well if you create your Objects using the above Class.create() syntax, then you will find a special helper method already defined in each of your Objects.
Manager = Class.create(Employee, {
//...
doWork: function() {
this.parent('doWork');
}
});
The parent method is defined internally when you create an Object using Class.create(). The parent method takes two parameters, the first is the method to call and the second optional parameter is an array of arguments to pass to that method.
this.parent('doWork');
this.parent('doWork', [param1, param2, ...]);
The parent method is inspired from the way messaging works in Objective-C and borrows some naming conventions from Ruby. It will look in the parent class for the given method. If it cannot find it there it will continue the search upwards in every parent class until it reaches the top of the chain. If at this point it still cannot find an implementation of that method it calls a specialmethod_missing method, which you can override to do something special if you so wish. The defaultmethod_missing implementation just throws an error.
Mixins
The last thing worth mentioning is the Class.mixin()
method, which is identical to Prototype's Object.extend()
method or the MochiKit.Base.update()
function. I believe mixin is a better name since, you are essentially mixing in the key:value pairs from another object. You can use this method to mixin common functionality. This works very similarly to mixin support you find in other languages such as Ruby.
Debug = {
log: function(msg) {
alert(msg);
}
};
Employee = Class.create({
initialize: function() {
this.log("In the Employee constructor!");
}
});
Class.mixin(Employee.prototype, Debug);
Client = Class.create({
initialize: function() {
this.log("In the Client constructor!");
}
});
Class.mixin(Client.prototype, Debug);
Now both Employee and Client objects have an identical log method.
var e = new Employee();
// alert with message "In the Employee constructor!" will be seen
var c = new Client();
// another alert with message "In the Client constructor!" will be seen
I have included class.js into a project I have on the go write now, and I plan on incorporating class.js into several other projects that have been put on the back-burner but should come back to life soon. The source is available via darcs, and more documentation will start appearing soon as well.
I have a few additional ideas for improving upon this extension, and there is still one final gotcha that needs to be overcome. I will leave that for another post however, since this one is already long enough.
And Finally, The Goods
Project page and just the inheritance.js file.
code repository: https://github.com/voidlock/inheritance
Posted on May 19th, 2007