OpenLaszlo’s LZX language is an attempt to blend the best of both class-based and prototype-based object-oriented programming to provide a powerful interface description language. It has classes, as traditional inheritance-based languages do, but it adds an interesting feature from delegation-based languages: the ability to extend the behavior of a single instance of a class. While it does not permit using an instance as a prototype for another instance, it does make it very easy to turn an extended instance into a class so more instances can be created, or the class further extended.
But, OpenLaszlo is built on Javascript. Which doesn’t (in the currently accepted standard) have classes. It is a prototype-based language.
So, Adam and I spent some time thinking about how to really do class-style inheritance right in Javascript. As he pointed out in his blog entry, the methods that are out there leave a lot to be desired. We know that class- and prototype-based inheritance are equivalent, so it shouldn’t be that hard.
Here’s what we came up with:
///
// Classes for Javascript
// Copyright 2006 Laszlo Systems, Inc. All Rights Reserved.
// Use is subject to license terms.
// Class factory. This is how you instantiate a class
Function.prototype.make = function () {
var i = new this();
i.init.apply(i, arguments);
return i;
}
// Add a method to a class by adding it to the class's prototype
Function.prototype.addMethod = function(methodName, methodFunction) {
this.prototype.addMethod(methodName, methodFunction);
}
// Compute the next applicable method. This is a method on Function
// so you invoke it by:
// arguments.callee.nextMethod('aMethod');
// To actually call the next method, you say:
// arguments.callee.nextMethod('aMethod').apply(this, ...);
Function.prototype.nextMethod = function (methodName) {
return this.nextClass.prototype[methodName];
}
// Bootstrap Class class
Class = function () { this.constructor = arguments.callee; };
// Add a method to an instance
// Records the 'next class' on this method for callInherited
Class.prototype.addMethod = function(methodName, methodFunction) {
this[methodName] = methodFunction;
methodFunction.nextClass = this.constructor;
}
// Default init method
Class.prototype.init = function () {};
// Class class factory. This is how you make a new class
Class.make = function (superclass) {
// The constructor notes itself in every instance
var nc = function () { this.constructor = arguments.callee; };
nc.constructor = Class;
if (arguments.length < 1) { superclass = Class; }
nc.prototype = new superclass();
return nc;
}
So, what’s this all about?
First, we want instantiation to be a factory. This gives you the possibility of instantiating an abstract class by creating an instance of a concrete subclass, or the possibility of creating a singleton class that only ever instantiates one object, or the possibility of creating a class of immutable instances where the same instance may be reused. For this reason, we define make as the factory method for instantiating a class.
Second, we want instantiation to happen in two phases: the first phase operates on the incomplete instance, initializing its attributes, without depending on the instance being initialized and functional; the second phase consists of initializations that depend on the instance being complete and can make arbitrary calculations involving the instance. For this reason, we define a second method init on all classes.
To implement the bookkeeping necessary for calling overridden methods, we define addMethod, which adds a method to a class, and nextMethod which computes the next less specific method from a current method.
Let’s look at each of these pieces in a little more detail.
First off, there’s that copyright. Don’t let that scare you away. Like all of OpenLaszlo, this code is available as open source under the Common Public License.
addMethod has two implementations. For an instance (which in our scheme is an instanceof Class), it simply records the function that implements the method on the appropriate property of the instance. It also annotates the method with the class context, to support computing nextMethod. The class context for an instance is its constructor — the class object that created it.
The second implementation of addMethod is for classes (which in Javascript are instances of Function). For a class, adding a method means adding the method to the prototype of the constructor. We’ll see in a bit why this magically works out to just calling addMethod on the prototype, since it is a Class.
Let’s have a look at the Class class factory to see how that works:
// Class class factory. This is how you make a new class
Class.make = function (superclass) {
// The constructor notes itself in every instance
var nc = function () { this.constructor = arguments.callee; };
nc.constructor = Class;
if (arguments.length < 1) { superclass = Class; }
nc.prototype = new superclass();
return nc;
}
When you call Class.make, it takes an optional superclass. A new class constructor is created. The constructor is defined to set the constructor property of every instance it creates to itself. This is a slight variation on normal Javascript. In normal Javascript, every object has a constructor property, but it usually inherits it from its (implicit) prototype. This mechanism has tripped up any number of attempts to create class-based inheritance schemes. Usually people complain that when they replace the prototype of a constructor with an instance of another (to create inheritance) that the constructor property is wrong. But, it is really right. Having the constructor property of the prototype reflect the constructor that really built it is how you can maintain the ‘superclass chain’. Instead, what we do is to override the prototype’s value by setting the value on each instance.
The second piece of Class.make is to create the superclass chain. If there is an argument, it is the superclass. An instance of that class is created and installed as the prototype of this class.
Now you can see how addMethod works on classes. Since the prototype of a constructor will have a constructor property that points to the superclass constructor, it is indeed the ‘next class’ in the superclass chain.
Now let’s look at nextMethod to see how this all hangs together:
// Compute the next applicable method. This is a method on Function
// so you invoke it by:
// arguments.callee.nextMethod('aMethod');
// To actually call the next method, you say:
// arguments.callee.nextMethod('aMethod').apply(this, ...);
Function.prototype.nextMethod = function (methodName) {
return this.nextClass.prototype[methodName];
}
Effectively, when addMethod is invoked, it remembers the nextClass that should be looked in for looking up overridden methods. So all nextMethod has to do is pluck out the prototype and look for the method there. This implementation has the nice property of using Javascript’s native prototype (or delegation) feature to find the next most applicable method, whether it is in the immediate superclass, or further on up the superclass chain. [If you want to get language-theoretical, the nextClass annotation is mimicing what a vtable pointer is used for in the C++ family of languages. Basically it is a bookmark remembering what superclass of the object you are currently treating it as.]
There are only three other little bits to our scheme. First, we have to have a place to stand to start, so we create the Class class by hand:
// Bootstrap Class class
Class = function () { this.constructor = arguments.callee; };
We then create the default make factory:
// Class factory. This is how you instantiate a class
Function.prototype.make = function () {
var i = new this();
i.init.apply(i, arguments);
return i;
}
This is on Function because all classes are constructors are Functions’s. This says that by default when you make a class, it just creates an instance and then applys the init method to that instance.
Lastly, there is the default init method:
// Default init method
Class.prototype.init = function () {};
Which does nothing but provied a method to be overridden by subclasses.
Here’s his test one. Very simple. We expect it to work. (I’ve rewritten Adam’s tests to use our new mechanism. Also, I’ve broken out the script from the button so it can be shared with subsequent tests.)
Test four passes with flying colors too. (Note that Adam had a type-oh in his original blog entry where he wrote that the correct answer was 8, not 6):
Okay. Reeeeeally long blog entry. Hope you were not completely bored. Now you have the basic structure of our new class inheritance scheme for Javascript. We think that it keeps with the spirit of Javascript’s prototype-based scheme, while giving the additional structure of class-based inheritance that programmers seem more comfortable with. Without a lot of overhead or caveats.
Thanks for signing in, .
Now you can comment. (sign out)
(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)
You are not signed in. You need to be registered to comment on this site. Sign in
Thanks for signing in, . Now you can comment. (sign out)
(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)