Many folks around the web are raving about Prototype, a Javascript library written by Sam Stephenson which features “a unique, easy-to-use toolkit for class-driven development and the nicest Ajax library around.” (I stole that line from the prototype site).
Prototype’s popularity is certainly due in part to it’s inclusion in current web darling Ruby on Rails, but that ought not to slag the package (or rails) one bit — I’ve used prototype a fair bit over at GeoBirds, and have become rather reliant on it. In many ways, it solves a ton of problems I didn’t realize I had. Heck, by simply shortening document.getElementById to $, prototype has saved me countless keystrokes, although one might rightly suggest I should have written myself a helper function earlier.
If you’re like me, however, you’re still rather troubled by prototype’s claim of “class-driven development” in Javascript. In fact, you’re probably a little fuzzy on the differences between object-oriented and prototype-based languages (hey, is that why they called it prototype?), and, if you’ve investigated them, you find approaches to enabling classical object-oriented development in Javascript rather unintuitive. (See here, here, and here, all good stuff, but rather painful to use).
Enter prototype’s solution: the Class object and Object.extend() method. Sam Stephenson uses them throughout the library (check out the source!), and they allow users like us to do simple object-oriented programming. For example:
// create an empty Person class
var Person =
Class.
create();
// define the Person interface
Person.
prototype =
{
initialize:
function(name) {
this.
name =
name;
},
show:
function() {
alert(this.
name);
}
});
var myperson = new Person(“Ken”);
// alerts "Ken"
myperson.show();
and
// create an empty Employee class
var Employee =
Class.
create();
// extend Person
Object.
extend(Employee.
prototype,Person.
prototype);
// extend Employee interface
Object.
extend(Employee.
prototype,
{
initialize:
function(name,salary
) {
// super constructor
Person.
prototype.
initialize.
call(this,
name);
this.
salary = salary;
},
show: function() {
alert(this.name + ‘ earns ‘ + this.salary);
}
});
var myemployee = new Employee(“Ken”, 100000000)
// alerts "Ken earns 100000000"
myemployee.show();
Not bad. No offence, but, in my opinion, prototype beats other Javascript OO approaches I’ve encountered. Still, I’m troubled that we have to define class creation, inheritance, and interfaces separately. Whatever happened to this?
class Person
{
protected var name;
public Person
(thename
) {
name = thename
}
public String show() {
return name;
}
}
class Employee extends Person {
protected var salary;
public Employee(thename,thesalary) {
super(thename);
salary = thesalary;
}
public String show() {
return name + ‘ ‘ + salary;
}
}
Seems a whole lot more intuitive to me, but, of course, that was Java, and the only relationship between Java and Javascript is a name suggesting either an Indonesian island or my early-morning brew. Luckily, a small extension to prototype gets us something similar. I call it Class.extend()
Object.
extend(Class,
{
extend:
function(source,additions
) {
var newclass =
Class.
create();
if ($C
(source
)) {
Object.
extend($C
(newclass
),$C
(source
));
}
Object.
extend($C
(newclass
), additions
);
return newclass;
}
});
function $C(object) { return object.prototype; }
Explain? Well, Class.extend() simply combines all the class definition steps of our earlier Employee extends Person example.
Using Class.extend, we can now say the following:
var Person =
Class.
extend({},
{
initialize:
function(name) {
this.
name =
name;
},
show:
function() {
alert(this.
name);
}
});
var Employee = Class.extend(Person, {
initialize: function(name,salary) {
$C(Person).initialize.call(this, name);
this.salary = salary;
},
show: function() {
alert(this.name + ‘ earns ‘ + this.salary);
}
});
Try it out (code). It’s a small extension, but I find Class.extend() saves me a ton of hassle. In fact, I now use Class.extend() by default and can nearly pretend I’ve never heard of object prototypes.