Making JavaScript act like a full-featured object oriented language takes some effort, typically more than I care to put forward. The process of manually typing out the prototyping of a JavaScript object has never been a favor task of mine.
function foo(args) {
// Constructor
};
foo.prototype.myFunction (args) {
// Do something cool.
};
foo.prototype.myOtherFunction () {
// Something less than cool but neat.
};
var box = new foo();
I find that code to be hard to read and a waste of time to write. What if the objects could be instantiated just a little closer other object oriented languages, something like:
$.declare(
'window.foo',
{
constructor: function (args) {
//
},
myFunction: function myFunction() {
//
},
myOtherFunction: function myOtherFunction() {
//
}
},
undefined // not required but included for example purposes
);
var box = new foo();
A bit simpler? Now, what about extending foo to include additional features? I want to be able to take foo as a base and extend it much like how PHP handles classes. How does this look?
$.declare(
'window.fooImproved',
{
myOtherOtherFunction: function myOtherOtherFunction() {
//
}
},
window.foo
);
var boxImproved = new fooImproved();
foo() and fooImproved() are completely independent classes (objects), with fooImproved taking all of foo’s properties and extending it with the additional myOtherOtherFunction() function. Now what’s really fun is inheritance with the addition of overloading functions.
Let’s take fooImproved() and make fooReallyImproved, complete with a new myFunction() with some other functionality.
$.declare(
'window.fooReallyImproved',
{
myFunction: function myFunction() {
// Do something incredible
this.inherited(arguments);
// Finish up
}
},
window.foo
);
The really neat feature about this.inherited() is that it can be called from any function to access the parent class’ features; if that same function exists in a parent class, the parent function is executed. this.inherited() can be called recursively all the way back to the base object.
So what is $.declare and how does it work?
$.extend({
/**
* Creates a pseudo-class object.
* @param mixClass Either a string-based object name (with or without dot notation) or an object
* @param objProperties JavaScript object containing the features to be included in the new class
* @param objParentClass Optional parent class to inherit from.
* @return object
*/
declare: function declare(strDeclaredClass, objProperties, objParentClass) {
// Establish defaults
objProperties = objProperties || {};
objParentClass = objParentClass || null;
// Use an anonymizer to generate a clean constructor (and prototype)
var obj = (function () {
return $.extend(
// Generic base constructor used by all objects
function constructor(args) {
// Make a unique copy of each variable in the class; prevents shared
// value strangeness between objects of the same inheritance.
for (var strLabel in this) {
if (!$.isFunction(this[strLabel])) {
this[strLabel] = $.extend(
true,
this[strLabel] instanceof Array
? []
: {},
this[strLabel],
strLabel === 'args'
? (args || {})
: undefined
);
}
}
this._constructor(args);
},
{
// Add functionality to our object for referencing the parent class
extend: function (properties) {
for (var intArg = 0; intArg < arguments.length; intArg++) {
for (var strLabel in arguments[intArg]) {
var objFunction;
if ($.isFunction(objFunction = arguments[intArg][strLabel]) && !0[strLabel]) {
objFunction.name = strLabel;
objFunction._inherited = (
this.prototype[strLabel] &&
$.isFunction(this.prototype[strLabel])
)
? this.prototype[strLabel]
: null;
}
}
this.prototype = $.extend(
true,
this.prototype,
arguments[intArg]
);
}
},
// Fresh prototype
prototype: {}
}
);
})();
// Duplicate prototype functions and properties
obj.prototype = $.extend(
true,
obj.prototype,
// Duplicate the prototype of the parent class as a starting point for building the new class
(objParentClass) ? objParentClass.prototype : {},
{
// Note the name of the class being created
declaredClass: strDeclaredClass.toString(),
// Parent class, solely for debug purposes
parentClass: (objParentClass) ? objParentClass.prototype.declaredClass : null,
// The _constructor is the constructor provided for the object,
// and is called by the generic constructor declared above.
_constructor: $.extend(
(objProperties.constructor.toString().indexOf('[native code]') < 0)
// Use the provided constructor
? objProperties.constructor
// No constructor provided; attempt to execute our parent's constructor
: function constructor(args) {
this.inherited(arguments);
},
{
// Provide the ability to call the inherited constructor
_inherited: (objParentClass) ? objParentClass.prototype._constructor : null
}
),
inherited: function inherited(objOriginalArguments) {
if ($.isFunction(objOriginalArguments.callee._inherited)) {
objOriginalArguments.callee._inherited.apply(this,arguments);
} else if (
objOriginalArguments.callee.name &&
objOriginalArguments.callee.name != '_constructor' &&
objOriginalArguments.callee.name != 'constructor'
) {
console.error(this,"::",objOriginalArguments.callee.name," doesn't have an inherited function.");
}
},
toString: function toString() {
return this.declaredClass.toString();
}
}
);
// Extend with passed properties
obj.extend(objProperties);
// Store the class
return $.setObject(strDeclaredClass, obj);
}
});
Note: the $.declare does rely upon jquery-getobject for the $.setObject() functionality. (thanks, Marcus Dalgren, for pointing out that I neglected mentioning this)
Many thanks to:
Tags:
JavaScript,
jQuery,
JSON,
object oriented programming,
objects,
Prototyping