Typical Noise

Alex Arnell

Coffee fueled terminal junky. A Software Developer with a strong passion for good software design and an unquenchable thirst for learning.

Javascript OO Ruby Style

This is a follow up post regarding the OO inheritance javascript library I blogged about earlier. I planned on submitting my original work as a patch to Prototype. Before submitting my patch at the rails development site I checked out the competition. I found a couple OO implementations already in contention for the next major version of Prototype. For the curious, both of them are mentioned on Sam Stephenson's weblog.

I spent some time looking over both solutions. Each one tried and did a very good job of implementing OO features, but they both had parts I didn't like. One author thought it would be neat to add support for private methods. This is neat, but I don't think javascript really needs this. People have been writing javascript for years without it and every attempt I've seen to add it to the language has seemed kind of hackish, well at least to me. You'll find my biggest beef with both libraries when you inspect an instance of one of your classes. You'll find a couple added methods and properties that the library injects so that it can do it's job. In fact even my previous OO library was guilty of this, something that bugged me to no end.

So I sat down one evening and rewrote the core of my own library to meet all my demands and expectations. I am proud to say that the new library is much smaller, sleeker and generally just feels better than the first. I also added some neat new features along the way.

Visit the project page for details on how to get the library.

Just like Dean Edwards, I want an OO library that solves a number of problems.
For convenience, here is Dean's list.

http://dean.edwards.name/weblog/2006/03/

  • I want to easily create classes without theMyClass.prototype cruft
  • I want method overriding with intuitive access to the overridden method (like Java’s super)
  • I want to avoid calling a class’ constructor function during the prototyping phase
  • I want to easily add static (class) properties and methods
  • I want to achieve the above without resorting to global functions to build prototype chains
  • I want to achieve the above without affectingObject.prototype

However, I have a few additions.

  • I want a more Ruby like feel to creating and working with Objects.
  • I want to achieve all of the above without adding additional baggage to final object.
  • I want to achieve all of the above while maintaining backward compatibility with Prototype.

So what's so new and great with my OO library? Well, since creating the first version I've learned a few neat tricks to help achieve my goals. First lets talk about mixins. Previously you had to define your mixins on a separate line
after your class definition.

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);

Well I've added a bit of syntax sugar that in turn adds more Ruby flavor. It’s now possible to statically define an objects mixins within the class definition.

Comparable = {
  // define methods...
};
Debug = {
  // define methods...
};
Employee = Class.create({
  // define a single mixin
  include: Debug,
  initialize: function() {
    // ...
  }
});
Client = Class.create({
  // define an array of mixins
  include: [Debug, Comparable],
  initialize: function() {
    // ...
  }
});

Each element given to the include property will have all of it's methods mixed into the objects' s definition. All standard rules for Ruby mixins are followed and apply when added this way. Also, it's worth noting that the include property will be removed from the object's definition. This way you won't see it when you inspect instances of your class.

The second improvement is in the this.parent() method. It's now smart enough to recognize what method it is being used inside of, so you no longer have to explicitly tell it what method to call.

Manager = Class.create(Employee, {
  initialize: function(name, dept, title) {
    this.parent(name, dept);
    this.title = title;
  },
  doWork: function() {
    this.parent();
  }
});

If you have defined a property or method using the same name, it will overwritten. It's also important to note that the 'this.parent()' method is only available inside a method definition. Doing something like the following will result in a javascript error.

var joe = new Manager();
joe.parent();

The last visible difference between this version and my previous is that I no longer force properties into the generated objects. This is best demonstrated with an example.

Debug = {
  log: function(msg) {
    document.writeln(msg);
  }
}

Employee = Class.create({
  initialize: function(name, dept) {
    <a href="http://this.name">this.name</a> = name;
    this.dept = dept;
  },
  doWork: function() {
    return <a href="http://this.name">this.name</a> + " is now working...";
  }
});

Manager = Class.create(Employee, {
  include: Debug,
  initialize: function(name, dept, title) {
    this.parent(name, dept);
    this.title = title;
  },
  doWork: function() {
    this.log(this.parent() + "at managing the employees.");
  }
});

var joe = new Manager("Joe", "Sales", "Sales Manager");
joe.doWork()

var out = "";
for (var p in joe) {
  out += p + ": " + joe[p] + ",\\n"
}
out = out.substring(0, out.length - 2);
Debug.log("Manager {\\n" + out + "\\n}");

Outputs:

Joe is now working...at managing the employees.
Manager {
  name: Joe,
  dept: Sales,
  title: Sales Manager,
  initialize: function (name, dept, title) {
    this.parent(name, dept);
    this.title = title;
  },
  doWork: function () {
    this.log(this.parent() + "at managing the employees.");
  },
  log: function (msg) {
    document.writeln(msg);
  }
}

As you can see the for (var p in joe) construct returns exactly what you defined. There are no extra methods or properties visibly added to you object to make any of this work. You can also see that the log method was mixed into the class definition and the include property no longer exists.

Visit the project page for details on how to get the library.

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