views:

45

answers:

1
<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:

  1. 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.
  2. 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.
  3. 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:

  1. Prior to instantiation, BaseClass exposes no public methods. This is to be expected. The methods exposed via a class constructor's return statement won't appear until it's instantiated. I'm good with that.
  2. Prior to instantiation, SubClass exposes the methods from BaseClass. This is exactly what I expected.
  3. Once I declare an instance of BaseClass, it has all the members I would expect. It has its typeName and BaseFunction methods.
  4. Once I declare an instance of SubClass, it has only those methods returned by its constructor's return 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();
+2  A: 

Your constructors are not working as constructors. When you use the new keyword Javascript creates the new object and expects your constructor to populate it with members etc. Your constructors are returning another new object, not related to the object which is associated with the constructor usage, thus the prototype is not working.

Change your constructor to something like

function FinalClass() {
    this.FinalFunction = function() { return "FinalFunction"; };
    this.TypeName = "FinalClass";
    this.SomethingSpectacular = function() { 
        return FinalClass.prototype.SubFunction.call(this);
    };
}

and you should see the expected inheritance behaviour. This is because the FinalClass method in this case alters the contextual object created via the 'new' mechanism. Your original method is creating another object which is not of type FinalClass.

Toby
Earlier, I had suspected that it might have something to do with the way I was returning a JSON-esque object from the constructor. I had discarded the idea, though, since so many other online samples seemed to be using it just fine. I'll give it a shot and see what happens! Thanks!
Mike Hofer
Dude, you are the man! :D That was the problem! WOOT! Thanks!!!!!
Mike Hofer
@Mike: yeah, online sample code on the topic of the JavaScript inheritance model is uniformly terrible and confused. For a (long!) discussion, see [this question](http://stackoverflow.com/questions/1595611/how-to-properly-create-a-custom-object-in-javascript).
bobince