DontEnum ]DontEnum ]
]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?
true; | Browser: | Internet Explorer | Mozilla | Opera | Safari 2 | Safari 3 |
|---|---|---|---|---|---|
| Result: | true; | true; | true; | true; | true; |
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.
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(" ");
})();
prototype; constructorprototype; (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(", ");
})();
constructor, true(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.
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.
(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;
}
};
})();
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: How to Set DontEnum