inheritance in JavaScript
First, a brief historical recap:
- Back in January, I admitted my man-crush on SSJS, and mentioned that IBM's implementation of it in XPages supports closures.
- In February, I wrote a sprawling essay about what closures bring to JavaScript, and why I think they're so useful.
- In June, I posited that SSJS isn't really JavaScript, explained what I felt to be the most pertinent differences, and promised to post my preferred approach to inheritance in JavaScript... but (until now) never did.
- A few weeks ago, Peter Presnell sparked a fascinating discussion about closures and the prototype property, and what role each might serve in maintaining an object inheritance hierarchy in JavaScript. For some reason I can't seem to find that post on his blog at the moment, which is a shame, because it really was a great discussion. Nonetheless, it reminded me that I had been meaning to post something about inheritance, and ever since I've just been waiting to find enough time to do so.
function Widget(widgetName){
var privateWidgetId = 1;
return {
getName: function(){
return widgetName;
},
getPrivate: function(){
return privateWidgetId;
}
};
}
When called (either with or without the new keyword), this Widget function returns an object. This object has two methods (getName and getPrivate). Each method demonstrates its access to a private variable via closure: getName has access to widgetName because a function's arguments are bound by closure to an object returned from that function; similarly, getPrivate has access to privateWidgetId because variables defined within a function are bound by closure to an object returned from that function.
So this is one model available to us for creating classes within JavaScript (including SSJS): we create a function that behaves as the constructor for any class instances, and within the constructor we create an object that serves as the class instance and return it. Any properties or methods defined within that return object are public, any properties or methods defined outside of the return object but still inside the constructor are private: they can be accessed and modified by the object's public methods but not from "outside" the object. Very handy... but what if we want to "subclass" this class?
function Hoozit(hoozitName, hoozitType){
var privateHoozitId = 2;
// call parent class constructor:
var thisHoozit = Widget(hoozitName);
// add a new method:
thisHoozit.getType = function(){
return hoozitType;
};
// override a parent method:
thisHoozit.getPrivate = function(){
return privateHoozitId;
};
return thisHoozit;
}
This example illustrates one approach (though not quite my favorite) to inheritance: we create a constructor function that defines a return object by calling the constructor of a parent class. Optionally, we can also add new private members inside the constructor for the child class and extend the return object with new (or overridden) public members. This model is sometimes called "parasitic" inheritance. Perhaps worthy of note is that any private members introduced within this constructor are not accessible to methods defined within the parent class. This might already be obvious, but just to drive the point home: if I instantiate a Hoozit, its getName method cannot access privateHoozitId because that method is added to the return object within the Widget constructor function, so that variable does not exist within that scope. Similarly, the newly introduced getType method does not have access to privateWidgetId because it is defined within a different function scope. Nathan tells me that this means these variables are actually considered "protected", not private. I guess that's a good thing... just keep these scope issues in mind when you're defining your own classes.
Again, I like the above model, but it's not quite my favorite - my only criticism is that, when adding or overriding a large amount of public members to the descendant object, this just seems to require an excessive amount of duplicate object references. The good news is that we can add a handy helper function to streamline the descendant construction a bit but maintain the same result when each object is created:
var Inheritance = {
extend: function(parentInstance, extensions){
for (var thisExtension in extensions) {
parentInstance[thisExtension] = extensions[thisExtension];
}
return parentInstance;
}
};
There's not much going on here, but it packs a useful punch: simply put, we pass an instance of an object we want to extend (parentInstance) and an object consisting of what we want to add (extensions). This function loops through all the public members of the extension object and adds them to the parent instance, then returns the modified object. So here's an example almost identical to the Hoozit, but with less syntactical duplication:
function Whatzit(whatzitName, whatzitType){
var privateWhatzitId = 3;
return Inheritance.extend(Hoozit(whatzitName, whatzitType), {
getType: function(){
return whatzitType;
},
getPrivate: function(){
return privateWhatzitId;
}
});
}
You may have noticed that it's the exact same number of lines of code, but there's one subtle difference: we don't have to name the return object just so that we can refer to it over and over again when we stamp it with each additional member (and, again, the advantage of this really only becomes obvious when defining a large amount of members in any given extension layer). We simply define a single object with all of the members we want to add, then pass the result of the parent constructor and the extension object to Inheritance.extend, and out pops an object that has all of the public members of the parent class (except those we've overridden), each with access to any protected members defined within that parent constructor, as well as any new methods we've defined in this constructor, each potentially with access to its own set of protected members.
I've concocted a fancier inheritance model that uses function memoization (not to be confused with memorization) and a few other tricks to provide support for basic object reflection, "super" methods, and such. But what we've definitely learned over the past year is that, in the context of XPages, an application runs best when its code is structured such that each language used is being asked to do what it's best at (imagine that). Hence, for basic inheritance models, the above pattern is perfectly sufficient... but if you're ninja enough to be maintaining deep inheritance hierarchies and ancestry-sensitive conditional processing in your server-side code, you might as well just use Java.

Comments
Here is the discussion you couldn't find on Closures:
{ Link }
Bruce
Posted by Bruce Elgort At 07:34:42 PM On 11/15/2009 | - Website - |
Posted by Tim Tripcony At 07:37:55 PM On 11/15/2009 | - Website - |
Would love to see the take on memoization, and writing like this I think having the superclass reference is very important.
"but if you're ninja enough to be maintaining deep inheritance hierarchies and ancestry-sensitive conditional processing in your server-side code, you might as well just use Java."
Blasphemy
Posted by Rich Waters At 04:03:40 PM On 11/16/2009 | - Website - |
Posted by Nathan T. Freeman At 08:07:15 PM On 11/16/2009 | - Website - |