Class-based OOP in Javascript done right

Max, Adam and I have been working on a scheme to support OpenLaszlo's LZX language in pure Javascript. As explained in our blog entries, LZX is a blend of class-based and prototype-based object-oriented programming, with an emphasis on class-based programming (because that seems the more widely accepted paradigm). Our current project is to build a new back-end for the LZX compiler to emit "browser Javascript" (i.e., Javascript that will run in all browsers) and to adjust the OpenLaszlo Runtime to also run in browser Javascript. The goal is to make OpenLaszlo run directly in the browser as any good AJAX platform should.

My post over the weekend was our latest version, but right after I posted it, I realized there was still a lot of room for improvement. The basic idea was there, but as Max observed, it's really not nice to modify the built-in Javascript classes. One might even claim that Object.prototype is verboten. So I spent a little time re-working what I posted, and now I have a really clean version (which I'll show in a minute).

Adding class-based inheritance to Javascript is a popular pastime. Probably because so many more people are familiar with class-based than prototype-based inheritance. (As I pointed out with my reference to the Treaty of Orlando, they each have strengths and weaknesses -- neither is the clear winner).

We're not trying to replace Javascript's inheritance mechanism. We're actually just trying to build on it and make it a little more intuitive for everyone who is used to class-based inheritance. We're releasing our work as open source to the community (under the CPL license), in hopes of building a consensus that all AJAX platforms could build upon.

Okay, lets cut to the chase. Here's the latest version of the code with some explanatory material interleaved.

///
// Classes for Javascript
// Copyright 2006 Laszlo Systems, Inc.  All Rights Reserved.
// Use is subject to license terms.

As I mentioned, the license terms are CPL.

// Bootstrap Class class
Class = function () { this.constructor = arguments.callee; };

We make a new class, Class, that will be the factory for creating new classes.

// Bootstrap Instance class
Instance = function () { this.constructor = arguments.callee };
// Instance is a Class
Instance.constructor = Class;

We make a new class, Instance that will be the superclass of all instances of all classes.

// Add a method to an instance
// Creates a closure attached to the method that can be used to
// invoke the next most applicable method by:
// arguments.callee.nextMethod('aMethod').apply(this, ...);
Instance.prototype.addMethod = function(methodName, methodFunction) {
  this[methodName] = methodFunction;
  var owner = this;
  methodFunction.nextMethod = function (methodName) {
    // defer evaluation to permit class change
    return owner.constructor.prototype[methodName];
  }
}

This method is the core of the scheme. When you add a method to an instance of a class using addMethod it annotates the method with a nextMethod function that can be used to invoke the next most applicable method. Normally in class-based OOP, you can't add methods to instances, but this is one of the features of prototype-based OOP we really want to retain.

// Default init method
Instance.prototype.init = function () {};

Any class can override the init method to customize initialization of the class. This method is invoked by the class instance factory once the instance is fully initialized.

// Add a method to a class by adding it to the class's prototype
Class.addMethod = function(methodName, methodFunction) {
  this.prototype.addMethod(methodName, methodFunction);
}

Here's the way you add a method to a class that will be available to all instances of that class (even if you add a method to the class after the instance has been created -- another feature we retain from prototype-based OOP). How do you do it? You just add the method to the class prototype (see above). You might need to draw a picture if you really want to understand how this works.

// Default Instance factory.  This is how you make a new instance
// of a class
Instance.make = function () {
  var i = new this();
  i.init.apply(i, arguments);
  return i;
}

Here's how you make a new instance of a class. A class can override this method to create a singleton class, or to create a class of immutable instances that can be reused.

Finally, we tie it all together with the Class class factory -- the factory that makes new instances of the class Class. As explained in my blog entry, we believe that a factory is a better approach than new, because it gives us better control over creation and initialization. [We notice that many Javascripts support returning a value from new, if this becomes an accepted standard, it would work just as well.]

// 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; };

As mentioned in my blog entry, our constructors set the constructor property on each instance to reflect the actual constructor, because when you have inheritance by delegation, the constructor property of your prototype will be your superclass, not your class.

  nc.constructor = Class;

So we do that. We are making a new Class, so we make its constructor be Class (not Function as it would be by default).

Next we add two class methods to the new class:

  nc.addMethod = this.addMethod;

You add a method to any class using the Class.addMethod method. This needs to be a class method so you don't get the instance method which is on the Class prototype.

Finally, we set the prototype for the class to be the superclass that was passed in, or, if there isn't one, to be Instance. All classes are thus instanceof Instance. (They are also instanceof Object, since Instance is an object.) And add the second class method.

  if (arguments.length < 1) { superclass = Instance; }
  nc.make = superclass.make;
  nc.prototype = new superclass();
  return nc;
}

Note the class make method is defaulted to that of its superclass. By default this will be the instance factory, although it can be overridden by the class.

That's it! No built-in prototypes need to be modified. Everything is encapsulated in the classes Instance and Class. For the record, I've included Adam's most complex test case below, that shows how you write class-based style code using our system, and that the system works.

<script type="text/javascript" language="javascript1.5">
// <![CDATA[

Make a top class with one method

    mysuper = Class.make();
    mysuper.addMethod("amethod", function (){ return 1; });

Make a subclass of that, overriding the superclass method,
but also calling it using nextMethod

    mysub = Class.make(mysuper);
    mysub.addMethod("amethod", function(){
        return 10 + arguments.callee.nextMethod( "amethod" ).call(this);
    });

Make a sub class of that, with another override

    mysubsub = Class.make(mysub);
    mysubsub.addMethod("amethod", function(){
        return 0.1 + arguments.callee.nextMethod( "amethod" ).call(this);
    });

Add some more methods. Note that this method, on the
top-most class, calls another method, which should be the
_most_ _specific_ method according to the runtime class of
this, which in this case will be mysubsub.dmethod.

    mysuper.addMethod("cmethod", function ( ){
        return this.dmethod();
    });
    mysuper.addMethod("dmethod", function ( ){ return 0.1; });
    mysub.addMethod("cmethod", function ( ){
        return 1 +
        arguments.callee.nextMethod( "cmethod" ).call(this);
    });
    mysub.addMethod("dmethod", function ( ){
        return 10 +
        arguments.callee.nextMethod( "dmethod" ).call(this);
    });
    mysubsub.addMethod("cmethod", function ( ){
        return 100 +
        arguments.callee.nextMethod( "cmethod" ).call(this);
    });

This will be the method called by mysuper.cmethod. If
inheritance is working properly, nextMethod should call the
corresponding mysub method. This is where some other
class-based schemes break down, as Adam showed.

    mysubsub.addMethod("dmethod", function ( ){
        return 1000 +
        arguments.callee.nextMethod( "dmethod" ).call(this);
    });
    asubsub = new mysubsub();
// ]]>
</script>
<button onclick='
    alert( "asubsub.cmethod( ) == 1111.1? " +
        ( asubsub.cmethod( ) == 1111.1 ) + "\n" +
        "It is really: " + asubsub.cmethod( ) );
'>test five
</button>

Try it:

8 Responses to “Class-based OOP in Javascript done right”

  1. RV Says:

    Questions about scalability :
    - Is anyone using laszlo to run ondemand applications (ie Software as service applications ) ?
    - what are the scalability benchmarks,if any, on running this on a j2ee platform ( a very broad question ,but any available information will be useful).
    - is laszlo meant only for intranet applications ,due to performance reasons ?

    thanks in advance

  2. Simon Says:

    I think your questions are not going to be answered my friend. No coder will answer you because your questions are not appropriate for a blog like this. They just insult web coders at large. I think the better way would be to start reading the source code and then figure out for yourself what it all means. Unless you have coded a web application using JavaScript, HTML, CSS, DOM, a webserver, a server-side scripting language like PHP and a database, I don’t think you will ever get the answer you want. Sorry.

  3. Kaj Kandler Says:

    Looks like a nice way to do it.

    Although having struggled through other ways of doing class based inheritance, I have learned that if you can do it is not the same as if the language is based on it. It simply means that all components of your system have to support the same way to do it. Which burns you more often than not.

    What I’m trying to say is we should strive for JavaScript 2.0 to include the propper way to do Class based inheritance.

    K
    P.S.: I’m missing a “A is intance of class B construct”.

  4. jgrandy Says:

    Kaj - I agree. The best thing is for the language to support a construct like this natively. We are closely following any ECMA activity around Javascript and will support any class syntax that arrives through the standards process. However we have a somewhat unique need — we compile to multiple runtimes and have to support the same LZX language features across all those runtimes. To that end this library is intended to bring a class model back to ECMA-262 dialects. Note that we aren’t proposing Javascript syntax here — our LZX compiler will still generate ECMA-262 — we’re describing a way of achieving reasonable semantics.

  5. Brendan Eich Says:

    ECMA-262 Edition 3 (http://www.mozilla.org/js/language/E262-3.pdf among other places) specifies (13.2.2 [[Construct]] in function objects, step 7) that the result of new C for a function C is the value returned from C if that value is an object.

    What browsers fail to get this right?

    /be

  6. OpenLaszlo Project Blog » Does your Browser conform? Says:

    [...] Brendan Eich asks, in a comment on Class-based OOP in Javascript done right: ECMA-262 Edition 3 (http://www.mozilla.org/js/language/E262-3.pdf among other places) specifies (13.2.2 [[Construct]] in function objects, step 7) that the result of new C for a function C is the value returned from C if that value is an object. What browsers fail to get this right? [...]

  7. ptw Says:

    It works in all the browsers I can test (see Does your browser conform? to try your favorite browser). But it does not work in Flash, which is one of our target runtimes.

  8. OpenLaszlo Project Blog » Legals PR3: Replacing the tablecloth without touching the place settings Says:

    [...] Converted all of the OpenLaszlo Runtime to use the proposed ECMA-4 class declaration syntax, and in the process modified them all to directly work with the new OpenLaszlo class model; [...]


Copyright © 2005-2010 Laszlo Systems, Inc.