Wow. It’s been almost 2 years since I’ve finished a technical blog post! I’ve started plenty, but have just never quite finished any. I figure it’s either because I try to tackle too much in a single post and/or because I am way too picky about the topic. Anyways, here’s a new blog post to prove I didn’t actually fall off the face of the Earth!
In my previous post, I laid out the basic Javascript inheritance mechanism, prototype chaining. In this post, I’ll elaborate on it a bit more by showing how I took this basic concept and created something workable for some very bright, but prototype-phobic programmers: Oracle Life Sciences division’s Java developers! (Java and Javascript couldn’t be more different in almost every regard, especially concerning inheritance.)
Even though I had been using Javascript quite heavily since 1996, I was clueless to its object-oriented capabilities until 2004. That’s when I discovered prototype.js, one of the first Javascript frameworks — and still one of the best, imho. One of the reasons I love prototype.js is because it extends the native Javascript objects, rather than wraps them. (This, also, btw, is one of the aspects of prototype.js that people don’t like.) But to me, it just feels so much more powerful, natural, and expressive. This is the way that Javascript was meant to be used!
Well, that said, I plan to talk about how I gleaned some knowledge from an exception to prototoype.js’s extend-rather-than-wrap rule in version 1.3. The Class.create() method breaks from prototype.js’s usual m.o. and hides the native, natural prototypal inheritance of Javascript with a more classical-looking inheritance mechanism (e.g. more Java-like). At first, I hated that prototype.js did this — but so did every other major framework.
However, I warmed up to it when I was tasked to create a new Javascript framework from scratch at Oracle. The reason for my change-of-heart is simple: wrapping the inheritance mechanism creates an opportunity to do magical things and reduces the overall code size at the same time.
Primer / reminder: prototypal inheritance uses slightly different concepts from classical OO:
- constructor <==> class
- prototype members <==> class members
OK. Time for some code. Our wrapper function looked a lot like this:
Object.Extend = function (/* Function|Object */ base, /* Function */ construct) { var baseIsObj = typeof base == 'object'; construct.prototype = baseIsObj ? base : new base(); construct.prototype.superclass = baseIsObj ? base.constructor.prototype : base.prototype; return construct; } |
In summary, Object.Extend takes a base constructor function or prototype object (base) and a new constructor function (construct). As you would expect, it chains the prototype of the new constructor function to the base constructor function, which allows the objects created with the new constructor to inherit members from the old constructor. Nothing new here.
var MySubClass = Object.Extend(MyBaseClass, function () { /* do stuff here */ }); |
If an object is passed instead of the base constructor, it is used directly as the prototype. Typically, the developer would pass the prototype from an existing base constructor or an object literal to serve as a prototype. However, as an interesting twist, the developer could choose to pass an instantiated object from a previously-defined constructor. This can provide some very interesting object hierarchies, but likely with unintentional consequences. (This might be a good topic for a later blog post.)
More importantly, our wrapper method gave us the opportunity for some syntactic sugar. We’ve added a new property to our new constructor’s prototype: superclass. This property allows us to access our object’s immediate ancestor prototype directly. By default, Javascript forces the developer to know the ancestor prototype in advance, which can be problematic or simply impossible in polymorphic situations.
Default Javascript method to access an ancestor prototype:
MyClass.prototype.someMethod = function () { AncestorClass.prototype.someMethod.apply(this, arguments); /* do other stuff here */ }; |
Our new method to access our immediate ancestor’s prototype:
MyClass.prototype.someMethod = function () { this.superclass.someMethod(arguments); /* do other stuff here */ }; |
Besides being more compact, our new method prevents us from having to know our immediate ancestor! R0x0r!
Wait a minute, I seem to remember that this isn’t really a freebie. I remember several issues that appeared when the ancestor’s implementation of someMethod called upon other methods or properties. The superclass property had to be used very carefully or it would result in unpredictable results or infinite loops (ugh, I think I need another blog post for this). In the Oracle framework, we used the superclass property very sparingly.
Another side-effect of our Object.Event wrapper is that we must allow our base constructor function to be able to take zero parameters. This was initially a show-stopper for me. I’d been so accustomed to passing initialization parameters to my classes in classical OO. It’s good programming practice to force developers to pass parameters that are necessary for proper object instantiation. At Oracle, we got used to calling an init() method on our objects immediately after instantiating them, passing any necessary parameters to this method.
This zero parameters problem is easily worked around for the general case by providing an instantiation method in the constructor’s prototype. This is precisely the way that prototype.js and some other frameworks do it. A call to
this.initialize.apply(this, arguments); |
occurs in the wrapper function.
It’s very interesting to note that prototype.jsalso implements a second-level of prototypal inheritance within it’s wrapper. It constructs an inner object and places it into the prototype chain. This solves some of the problems we had at Oracle and also handily solves the problems with the superclass property.
Wow. There is so much to talk about! I think I’ll take each of the major frameworks, prototype.js, jQuery, and dojo, and dissect their inheritance wrapper functions. What better way to understand the intricacies of object-oriented Javascript!