John Resig (of jQuery fame) provides a concise and elegant way to allow simple JavaScript inheritance.
It was so short and sweet, in fact, that it inspired me to attempt to simplify and improve it even further. I've modified Resig's original Class.extend function such that it passes all of his inheritance tests (plus a few more), and also has the following advantages:
- simplicity (less code and you don't have to be a ninja to understand it)
- performance (no extra overhead for base/super method calls)
- consistency (familiar C# syntax for base/super method calls)
- tool friendly (constructor syntax no longer crashes Visual Studio and provides complete "IntelliSense" support)
Because it almost seems too good to be true, I want to ensure my logic doesn't have any fundamental flaws or bugs, and see if anyone can suggest improvements or refute the code entirely.
Does anyone see anything wrong with my approach (below) vs. John Resig's original approach?
if (!window.My)
window.My = {};
My.Object = function()
{
/// <summary>Initializes a new instance of the My.Object class.</summary>
};
My.Object.isReservedKeyword = function(value)
{
/// <summary>Checks a string value against the list of reserved keywords.</summary>
/// <param name="value">The value to check.</param>
/// <returns>True if the value is a reserved keyword.</returns>
return /^(base|constructor|ctor)$/.test(value);
};
My.Object.extend = function(prototype)
{
/// <summary>Creates a new derived class by extending the calling base class.</summary>
/// <param name="prototype">The prototype constructor and members of the derived class.</param>
/// <returns>The new derived class.</returns>
// create the base class
var Base = function() {};
Base.prototype = this.prototype;
// create the derived class
var Derived = (prototype && prototype.ctor) ? prototype.ctor : function() {};
Derived.prototype = new Base;
Derived.prototype.base = Base.prototype;
Derived.prototype.constructor = Derived;
// extend the base prototype
for (var i in prototype)
if (!My.Object.isReservedKeyword(i))
Derived.prototype[i] = prototype[i];
// make the derived class extendable
Derived.extend = arguments.callee;
// return the derived class
return Derived;
};
And the usage (below) is nearly identical to John Resig's original usage except for the syntax around base method calls and the way the constructor is specified (for the reasons enumerated above). I've also included a few more usage examples and tests.
var Minimal = My.Object.extend();
var Simple = My.Object.extend(
{
exists: function()
{
return true;
}
});
var Person = My.Object.extend(
{
ctor: function(isDancing)
{
this.dancing = isDancing;
},
dance: function()
{
return this.dancing;
}
});
var Ninja = Person.extend(
{
ctor: function()
{
this.base.constructor.call(this, false);
this.attacking = true;
},
dance: function()
{
return this.base.dance.call(this);
},
attack: function()
{
return this.attacking;
}
});
var m = new Minimal();
var s = new Simple();
var p = new Person(true);
var n = new Ninja();
// Test 1
if (!(m.constructor === Minimal && m instanceof Minimal && m instanceof My.Object))
alert("Test 1 failed.");
// Test 2
if (!(s.constructor === Simple && s instanceof Simple && s instanceof My.Object))
alert("Test 2 failed.");
// Test 3
if (!s.exists())
alert("Test 3 failed.");
// Test 4
if (!p.dance())
alert("Test 4 failed.");
// Test 5
if (!(p.constructor === Person && p instanceof Person && p instanceof My.Object))
alert("Test 5 failed.");
// Test 6
if (n.dance())
alert("Test 6 failed.");
// Test 7
n.dancing = true;
if (!n.dance())
alert("Test 7 failed.");
// Test 8
if (!n.attack())
alert("Test 8 failed.");
// Test 9
if (!(n.constructor === Ninja && n instanceof Ninja && n instanceof Person && n instanceof My.Object))
alert("Test 9 failed.");
NOTE: The above code has been updated several times since I initially posted this question. The above represents the latest version. To see how it has evolved, please check the revision history.