<disclaimer>
What follows is the fruits of a thought experiment. What I'm doing
isn't the issue; the symptoms are. Thank you.
</disclaimer>
I've finally wrapped my head around constructors, prototypes, and prototypal inheritance in JavaScript. But the SomethingSpectactular method in the sample below bugs me:
function FinalClass() {
return {
FinalFunction : function() { return "FinalFunction"; },
TypeName : "FinalClass",
SomethingSpectacular : function() {
return FinalClass.prototype.SubFunction.call(this);
}
}
}
FinalClass.prototype = new SubClass();
FinalClass.constructor = FinalClass;
var f = new FinalClass();
The reasons this bugs me are:
- JavaScript apparently doesn't scan the prototype chain the same way for methods as it does for properties. That is,
f.SubFunction()
generates an error. - To get to a method on a prototype, you have to go through at least 3 dot operations every time you want to do it. FinalClass DOT prototype DOT Subfunctino DOT call. You get the point.
- Base class (prototype) methods aren't showing up in Intellisense the way I'd expect them to. This is highly annoying.
So the thought experiement was to determine what would happen if I wrote a version of inherits
that inserted stub functions onto the subclass that delegated back to the prototype for you. For example, it would automatically create the following function and add it to FinalClass
:
function SubFunction() { SubClass.prototype.SubFunction.call(this); }
Now, I've got just about everything mapped out and working. The inherits
method, which is an extension to both Object.prototype
and Function.prototype
, takes a Function
as its sole argument. This is the base class. The subclass is determined by analyzing Object.prototype.inherits.caller
.
From there, I set the prototype
and constructor
on the subclass, and then start analyzing the methods on the subclass's new prototype. I build an array containing the methods on both the prototype and the base class's public interface. The end result is a nice array that contains the methods that are exposed through either the prototype or by the base class constructor's return
statement.
Once I have that, I start looking for each method on the subclass. If it's not there, I add it to the subclass with the same name and signature. The body of the method, however, simply forwards the call to the base class.
Now, I can step through all this code and it works marvelously, up until I instantiate instances of the subclasses. That's when things get wonky. Here's what I've observed using Visual Studio 2008 (SP1) and Internet Explorer 8:
- Prior to instantiation,
BaseClass
exposes no public methods. This is to be expected. The methods exposed via a class constructor'sreturn
statement won't appear until it's instantiated. I'm good with that. - Prior to instantiation,
SubClass
exposes the methods fromBaseClass
. This is exactly what I expected. - Once I declare an instance of
BaseClass
, it has all the members I would expect. It has itstypeName
andBaseFunction
methods. - Once I declare an instance of
SubClass
, it has only those methods returned by its constructor'sreturn
statement. No members from the base class are present. All the work that was done in the inherits method to map base class methods to the subclass appears to have been lost.
The big mystery for me here is the disappearance of the methods that I added to SubClass during the execution of inherits. During its execution, I can clearly see that SubClass is being modified, and that BaseClass's functions are being propagated. But the moment I create an instace of SubClass, that information is no longer present.
I am assuming this has something to do with the constructor, or order of events, or something else that I am simply not seeing.
A Final Note: This project arose as an effort to understand the complexities of JavaScript and how its prototypal inheritance system works. I know there are existing libraries out there. I know I'm reinventing the wheel. I'm reinventing it on purpose. Sometimes, the best way to understand a thing is to build it yourself. This has already been a tremendous learning experience, but I'm just stumped at this one particular point.
THE CODE
sti.objects.inherits = function inherits(baseClass) {
var subClass = sti.objects.inherits.caller;
var baseClassName = sti.objects.getTypeName(baseClass);
var methods = sti.objects.getMethods(baseClass);
if(!sti.objects.isDefined(baseClass.typeName))
baseClass.typeName = baseClassName;
var subClass = sti.objects.inherits.caller;
var subClassName = sti.objects.getTypeName(subClass);
var temp = function() {};
temp.prototype = new baseClass();
subClass.prototype = temp.prototype;
subClass.constructor = subClass;
subClass.typeName = subClassName;
subClass.baseClass = baseClass.prototype; // Shortcut to the prototype's methods
subClass.base = baseClass; // Cache the constructor
for(var index = 0; index < methods.items.length; index++) {
var resource = methods.items[index].getFunction();
var methodName = methods.items[index].getName();
if(methodName != "" && ! sti.objects.isDefined(subClass[methodName])) {
var method = sti.objects.createOverride(baseClassName, resource);
subClass[methodName] = method;
if(typeof subClass.prototype[methodName] == "undefined") {
subClass.prototype[methodName] = method;
}
}
}
}
Object.prototype.inherits = sti.objects.inherits;
Function.prototype.inherits = sti.objects.inherits;
function BaseClass() {
return {
A : function A() {return "A";}
};
}
function SubClass() {
inherits(BaseClass);
return {
B : function B() { return "B"; }
}
}
var b = new BaseClass();
var s = new SubClass();