I’m doing a presentation on OO Javascript to Daugherty developers on the 27th. In preparation I was doing some digging online (mostly to make sure I really DO know what I’m talking about) when I ran into an article that talked about the prototype chain. While I was aware of this…I don’t know if it was ever spelled out for me…it was just…there. So it got me thinking about a different way to handle subclassing.
There are 2 primary forms of subclassing I’ve seen:
- Copy the methods from the SuperClass.prototype to the SubClass.prototype
- Set the SubClass.prototype to a new instance of the SuperClass.
I use the first and it actually works pretty well. The only problems I have with it are that it (probably?) won’t work with the instanceof operator (actually…I’ve never used it. I just learned about it, but I’m guessing it doesnt’ work), and that it gets hairy when you do multiple levels of inheritance and are trying to keep track of the heirarchy (so you can do stuff like super.someMethod();). But honestly…both of those are probably smells anyway. Don’t subclasses always know who theier superclass is anyway? The SubClass code could just say: SuperClass.prototype.blah.call(this, argv);…not ‘pretty’, but it works. The point is, that the subclassing via copying methods from the parent prototype works, but it still has its problems.
The second I’ve never liked. What if the SuperClass constructor needs arguments? What if it does something expensive, or needs the DOM, or any number of problems. The ’solutions’ I’ve seen to this are to add code to the SuperClass constructor to determine if it was called with no args. If it was, then do nothing. But really, this is only feasible for a constructor that takes args…what if it was a no-args, but did something expensive…or assumed some state of the browser that is not true at the time of the ‘extend’ call?
For whatever reason, the article about the prototype chain triggered something and got me thinking about a different approach. My first attempt was to just set the Subclass.prototype.prototype to the SuperClass.prototype. That didn’t work. I needed to know more, so I dug into the ECMA 262 spec…not sure why that hadn’t happened before. The spec talks about an IMPLICIT (i.e. not accessible) ‘[[prototype]]’ reference. In essence, every instance ALWAYS knows what prototype it was created based on. The prototype chain heavily relys on this implicit reference to resolve properties. This means that the prototype instance on the SubClass should really have a reference to the SuperClass.prototype for it to truly take advantage of the OO features that ECMA script is defining.
So, at face value that means setting SubClass.prototype to a new instance of the SuperClass. I still don’t like that. However, this is Javascript…where everything can be dynamic. So a new idea was forming. Why not create a new, in-line subclass of the SuperClass and set the SubClass.prototype to an instance of THAT in-line subclass instead? Here is the idea:
Class.extend = function(SubClass, SuperClass){
var innerSuperClass = function(){};
innerSuperClass.prototype = SuperClass.prototype;
SubClass.prototype = new innerSuperClass();
SubClass.prototype.constructor = SubClass;
}
This approach seems to be the best of both worlds…it builds a nice prototype chain, and does not require the SuperClass to be overly careful about calls to its constructor.
Is there something I’m missing here? Is there something about this approach that is just..bad, that I may have missed. Please let me know. I’m sure I’m not the first to do this. Until hearing otherwise, this is the approach I will be using.
February 8, 2006 at 9:32 am
That is very clever indeed. Not only does the instanceof operator work as expected, but if you add a method to the superclass’ prototype, it appears on instances of the childclass - sweet!
March 22, 2006 at 1:26 pm
Very interesting and elegant solution!
I’m scratching my head over one line of code, though. The last line is:
SubClass.prototype.constructor = SubClass;
For some reason I’m thinking that should be:
SubClass.prototype.constructor = SuperClass;
Was that a typo, or am I mixed up? (The latter is quite possible…)
Thanks!
March 22, 2006 at 1:52 pm
No, that is no typo…understandably confusing though
That line is ‘fixing’ the ‘constructor’ property for all instances of the sub-’class’. Without that line, the following would occur:
var sub = new SubClass();
alert(sub.constructor == SubClass); //alerts FALSE
alert(sub.constructor == SuperClass); //also FALSE since it really is the anonymous inner function
if the prototype.constructor was set to SuperClass, then you get the following:
var sub = new SubClass();
alert(sub.constructor == SubClass); //alerts FALSE
alert(sub.constructor == SuperClass); //TRUE, but should it be?
Basically, what is happening is that when the function is created by the engine, it is given a prototype property…and it holds a property ‘constructor’ by default that points to the original fn the prototype was created for. That way, all instances of that ‘class’ automatically have a ‘constructor’ prop that points to the original constructor function. When the prototype is set to an instance of a different ‘class’, the prototype for the original constructor function then becomes that instance…which has a constructor prop that points to ITS original constructor function. So we just need to change the constructor prop for that instance to be the SubClass function…since it is being used as the SubClasses prototype this should be fine.
The only headaches this really causes is when you need to programmatically traverse the prototype chain. For this purpose, the superClass prop was added. BTW, this problem would exist even in the classical “SubClass.prototype = new SuperClass()” approach.
The only other way to get the prototype fixed would be to actually set the ‘constructor’ prop on every instance. That would require all instances be created via a factory method or something similar. It would be doable if there was like a magic _new property of every function that got called for every instance…then you could say:
SubClass._new = function(){
this.constructor = SubClass;
}
And in that case, you would definitely want to set SubClass.prototype.constructor to SuperClass…and then prototype traversal wouldn’t need a ’superClass’ prop.
Sorry for the long-winded answer…I know its rather confusing. Hope I made it more clear.
August 6, 2007 at 2:10 pm
Whoah! Thanks!!
I’ve just spent the whole day struggling with this exact problem where
“ChildClass.prototype = new ParentClass();”
involves an expensive constructor which relies on the DOM and other global variables.
Hopefully your code will set things right.
Does this depend on a library (prototype.js, mootools.js etc) for Class? A library-independant example would be nice.
August 8, 2007 at 1:03 pm
That’s actually similar to a function within Google Maps (renamed to help me figure out what’s going on):
function aExtendsB( a,b ) {
var c = function() {};
c.prototype = b.prototype;
a.prototype = new c
}
August 8, 2007 at 6:24 pm
Awesome, thanks. I’ve also seen it crop up in the YUI framework. I’m glad to see that other, smarter folks have found the same approach.