JavaScript is a "quite old" scripting language but every day there's some
news about its normal behaviour or some common code ... well, today is my round :-)
There are a lot of frameworks or libraries
We use every day but I think every JS developer should know atleast basic JavaScript prototypal inheritance.
It seems to be the
ABC of this wonderful scripting language but if You'll read this post I suppose You'll be surprised!
Let me start with a simple example about JavaScript inheritance, a constructor
A with just one prototyped method and a sort of subclass
B that would be extend A one.
So, this is the A constructor:
function A(){};
A.prototype.constructorIs = function(F){return this.constructor === F};
// alert((new A).constructorIs(A)); // true
This is a basic constructor example and every instance will inherit constructorIs method.
This one will return
instance.constructor and compare them with a generic constructor Function.
Well, at this point We need just another constructor that would extend
A one ... and its name is, for a real fantasy example,
B.
function B(){};
B.prototype = new A;
Well done, this is a simple JavaScript inheritance example.
Every B instance will be an instanceof B and an instanceof A but there's a
strange JavaScript behaviour with this kind of inheritance:
every instanceof B will have a constructor property that will be exactely inherited one!
alert((new B).constructor === A); // true!!!
Since prototype inheritance works in
this way, last example code shows a correct behaviour.
This is true just because if
new Function inherits from
Function.prototype and every Object, as prototype parameter "
is", is assigned by reference and not by a copy.
However JavaScript has not an official or crossbrowser way to get last inherited instance constructor so this behaviour should be a problem when You write a bit complex code or use multiple inherited constructors (someone call them classes).
At this point if You choose to implement a generic constructor
extend method or function, I suppose You should clean this behaviour adding a better one ... or if You don't think so, please don't call that function/method
extend and choose another name such
injecting arbitraty methods or properties!
What I mean is that native JavaScript behaviour, using A and B constructors, is this one:
var a = new A,
b = new B;
alert([
a.constructorIs(A), // true
b.constructorIs(B), // false
a instanceof A, // true
b instanceof A, // true
b instanceof B, // true
a instanceof B, // false
a instanceof Function, // false
b instanceof Function // false
].join("\n"));
As I've said, this behaviour is logically correct but I suppose that every Object Oriented developer would have atleast one more
true in that boolean list:
an instanceof B should have a B constructor!
This is why a lot of people wrote something about prototypal inheritance but it seems that not everyone tested correctly expected
extend behaviour, that should be:
b.constructorIs(B), // TRUE
to be able to use every instance constructor to call, or apply, methods or, why not, to call or apply
constructor.prototype.constructor one!
// base constructor or generic Object prototype
// to call its super (parent) while there's one
Object.prototype.SuperCall = function(method){
var parent = this.parent,
result;
if(this.parent.prototype.parent)
this.parent = this.parent.prototype.parent;
result = parent.prototype[method].apply(this, [].slice.call(arguments, 1));
this.parent = parent;
return result;
};
// base constructor
function A(){};
A.prototype.init = function(){
this.isA = true;
return this;
};
// constructor B extend A
B = $extend(A, {init:function(){
this.isB = true;
return this.SuperCall("init");
}});
// constructor C extend B
C = $extend(B, {init:function(){
this.isC = true;
return this.SuperCall("init");
}});
// A, B and C instances
var a = (new A).init(),
b = (new B).init(),
c = (new C).init();
// inheritance test
alert([
a.isA, // true
a.isB, // undefined
a.isC, // undefined
b.isA, // true
b.isB, // true
b.isC, // undefined
c.isA, // true
c.isB, // true
c.isC // true
].join("\n"));
Above example uses a parent property that should easyly be a shortcut for
this.constructor.prototype.constructor.
If constructor is correctly implemented there's no reason to don't use multiple inheritance from last constructor to first extended one but this is possible only with
correct extend method.
This is my humil one, self packed to stay into 205 bytes ... it's behaviour is exactly expected one and it has been used to create above example.
$extend=function(b){var p="prototype",a=[].slice.call(arguments,1),f=function(){},i=0,k;f[p]=b[p];f[p]=new f;while(i<a.length){for(k in a[i])f[p][k]=a[i][k];++i};f[p].constructor=f;f[p].parent=b;return f};
To compare extend code I've not used last example but this simplest one:
function A(){};
A.prototype.constructorIs = function(F){return this.constructor === F};
B = $extend(A);
var a = new A,
b = new B;
alert([
a.constructorIs(A), // true
b.constructorIs(B), // true
a instanceof A, // true
b instanceof A, // true
b instanceof B, // true
a instanceof B, // false
a instanceof Function, // false
b instanceof Function // false
].join("\n"));
that's exactly the same I've used to do famous frameworks/lybraries tests.
Let's start with first one?
jQuery - V 1.1.3.1One of the best library I've ever used, intuitive and powerful but it seems that jQuery
didn't understand correctly what an extend function should do.
I'm talking about
$.extend one, used with more than a single argument:
function A(){};
A.prototype.constructorIs = function(F){return this.constructor === F};
B = $.extend(A,{});
var a = new A,
b = new B;
alert([
a.constructorIs(A), // true
b.constructorIs(B), // true
a instanceof A, // true
b instanceof A, // true
b instanceof B, // true
a instanceof B, // true
a instanceof Function, // false
b instanceof Function // false
].join("\n"));
quite cool John, but if I extend a constructor B starting from A, how can be an instanceof A at the same time an instanceof B???
prototype - V 1.5.1.1This is a framework that I've never
loved but I know this is a great one!
This is prototype behaviour (probably I didn't understand Object.extend usage?) :
function A(){};
A.prototype.constructorIs = function(F){return this.constructor === F};
function B(){};
B.prototype = Object.extend(B.prototype, A.prototype);
var a = new A,
b = new B;
alert([
a.constructorIs(A), // true
b.constructorIs(B), // true
a instanceof A, // true
b instanceof A, // false
b instanceof B, // true
a instanceof B, // false
a instanceof Function, // false
b instanceof Function // false
].join("\n"));
Well ... I suppose that this is not a real extend public static Object method ... please change its name!
MooTools - V 1.11Another wonderful framework, developed by a really good stuff ... but probably old prototype framework logic isn't yet totally removed? :-)
var A = new Class({
constructorIs:function(F){
return this.constructor === F
}
}),
B = A.extend();
var a = new A,
b = new B;
alert([
a.constructorIs(A), // false
b.constructorIs(B), // false
a instanceof A, // true
b instanceof A, // true
b instanceof B, // true
a instanceof B, // false
a instanceof Function, // false
b instanceof Function // false
].join("\n"));
Ok Valerio, I know constructor is always a sort of
baseMooClass one ... but there's no way to correct this strange way to inherit functions?
dojo - V 0.9Great dojo toolkit framework, in its new beta version, is able to produce my
strange code using SuperCall method with expected constructor but an inherited instance can't return false in a "
instanceof Super" check, don't You agree?
function A(){};
A.prototype.constructorIs = function(F){return this.constructor === F};
B = dojo.extend(function(){}, A.prototype);
var a = new A,
b = new B;
alert([
a.constructorIs(A), // true
b.constructorIs(B), // true
a instanceof A, // true
b instanceof A, // false
b instanceof B, // true
a instanceof B, // false
a instanceof Function, // false
b instanceof Function // false
].join("\n"));
Please dojo developers confirm us that this is an expected behaviour because it seems too much strange as behaviour :P
Ok, now it's time to speak about team or developers who probably did these tests many time before me, starting from Kevin Lindsey:
// Kevin Lindsey - Article of 2006/11/12 at 17:32:30
function A(){};
A.prototype.constructorIs = function(F){return this.constructor === F};
function B(){};
KevLinDev.extend(B,A);
var a = new A,
b = new B;
alert([
a.constructorIs(A), // true
b.constructorIs(B), // true
a instanceof A, // true
b instanceof A, // true
b instanceof B, // true
a instanceof B, // false
a instanceof Function, // false
b instanceof Function // false
].join("\n"));
Well done Kevin and
your old article is cool too :-)
Next developer is,
obviously, Dean Edwards and his even old
Base proposal:
// Base - V 1.0.2 (now serving 2.X)
var A = Base.extend({
constructorIs:function(F){
return this.constructor === F
}
}),
B = A.extend();
var a = new A,
b = new B;
alert([
a.constructorIs(A), // true
b.constructorIs(B), // true
a instanceof A, // true
b instanceof A, // true
b instanceof B, // true
a instanceof B, // false
a instanceof Function, // false
b instanceof Function // false
].join("\n"));
Last big library that "probably" recieved Douglas Crockford help is
YUI!:
// YUI! -. V 2.2.2
function A(){};
A.prototype.constructorIs = function(F){return this.constructor === F};
function B(){};
YAHOO.extend(B, A);
var a = new A,
b = new B;
alert([
a.constructorIs(A), // true
b.constructorIs(B), // true
a instanceof A, // true
b instanceof A, // true
b instanceof B, // true
a instanceof B, // false
a instanceof Function, // false
b instanceof Function // false
].join("\n"));
Well done guys and that's all folks!