Enumeration and Object Oriented JavaScript

 

*AnimTree
*Tabs
*GlideMenus
*DragLib

Creating a Subclass

This section explains the detail that I breezed over in the previous example of the prototype chain.

When creating a subclass, we replace the value of the prototype property of the subclass with an instance of the superclass. This adds the superclass's prototype to the object's prototype chain.

What do you think the answer will be?

(function(){
var result = [];

function F(){}
function G(){}
G.prototype = new F;

var g = new G();
var miscProps = [];
for( var prop in g )
    miscProps.push[prop];

return (g.constructor === F) + "; " + miscProps.join(" ");
})();

What do you will be returned?

Once again: Does this result make any logical sense?

Expected: true;

Browser: Internet Explorer Mozilla Opera Safari 2 Safari 3
Result: true; true; true; true; true;
Examining the Result

The fact that (new G).constructor === F; is true, by default of the language, is counterintuitive. It is even more strange to see that an object who's constructor is F can be an instanceof it's subclass.

The browsers all agree that g.constructor === F. This is correct, as per behavior defined in the spec.

The constructor Property Might be Enumerable

Might be? Is there any way to tell?

Not in IE. Method propertyIsEnumerable would work in compliant implementations, but not in IE.

We can see that the constructor property was lost. We can add this back, and it will show up in for in enumeration (in compliant implementations).

(function(){
var F_before = [], f_after = [];

F=function(){};
new F().constructor == F; // true.
//F.propertyIsEnumerable("prototype"); //true, linear_b and JScript say false.

// We should see only "prototype" here.
for( var prop in F )
    F_before.push(prop);

F.prototype = {};
new F().constructor == F; // false. Whoops, it was lost. Add it back.

F.prototype.constructor = F;

var f = new F;

// We should now see constructor as being enumerable
for( var prop in f )
    f_after.push(prop);


return F_before.join(" ") + "; " + f_after.join(" ");
})();

Result:

Expected: prototype; constructor

Common Sense: prototype; (N/A)

Browser: Internet Explorer Mozilla Opera Safari 2 Safari 3
Result: ; prototype; constructor ; constructor prototype; constructor prototype; constructor

In the next example, will the constructor property be enumerable?

(function(){
var result = [];

function F(){}
function G(){}
G.prototype = new F;

var g = new G();
g.constructor === F;// true, We've lost F!
G.prototype.constructor = G;// Partial fix. The constructor property is Enumerable

g = new G;
for( var prop in g ) 
    result.push(prop);
    
result.push(g.constructor === G);
return result.join(", ");
})();

Result:

Expected: constructor, true

Common Sense: (N/A), true

Browser: Internet Explorer Mozilla Opera Safari 2 Safari 3
Result: , true constructor, true , true constructor, true constructor, true

The constructor property should be the function that actually constructed the object. —David Anderson (web-graphics.com).

Apparently, I'm not the only one who thinks so.

Extending the Superclass without Calling the Constructor

The problem with assigning an instance of the super to the sub's prototype, is that the super's constructor is called. This can have unwanted consequences.

function Tree(el) {
       if (!el) { return; }
      // if we got this far then this was not an inital sub-class call
      // and we can continue instantiating the object
       ...
}

function TreeSubclass(el) {
       ...
}

TreeSubClass.prototype = new Tree(); // here is the problem avoided above

(Tree example provided by David Mark)

This works, but it ties our implementation of the constructor to the constructor chaining process. This puts too much burden on the programmer because it requires extra checks inside the constructor.

A better aproach is to create a dummy, noop interim function, assign the superclass's prototype to the noop, then assign a new instance of the dummy to the subclass.

function Tree(el) { }

function TreeSubclass(el) { }

// Dummy, noop interim function.
function F(){}

F.prototype = Tree.prototype.

TreeSubClass.prototype = new F();// Side-effect free!

Now we don't have to worry about side effects associated with invoking the Tree constructor. This technique can be put into a method and reused any time we want to create a subclass.

We still have to fix the missing constructor property.

Safe Inheritance

(function(){
Function.extend = function extend(subc, superc) {
    if (typeof subc != "function" || typeof superc.call != "function") {
         throw new TypeError("extend: subclass is not a function: " + subc);
    }
    if (typeof superc != "function" || typeof superc.call != "function") {
         throw new TypeError("extend: superclass is not a function: " + superc);
    }
   // Fix JavaScript's broken constructor.
    var F = Function();
    F.prototype = superc.prototype;
    subc.prototype = new F();
    subc.prototype.constructor = subc; 
    subc.superclass = superc;
    if (superc.prototype.constructor === Object) {
        superc.prototype.constructor = superc;
    }
};
})();

Constructor Chaining

We can now chain the constructor calls.

function Tree(el) { }

function TreeSubclass(el) {
    this.constructor.superclass.call(el)// Tree.call(el) would work, too.
}

Function.extend( TreeSubClass, Tree );
var treeSubClass = new TreeSubClass();

This is the approach that YUI uses, and it is clearly the best approach I've seen to date. This code can be put into a function.

Next in this tutorial: