Posted on: February 26th, 2010 by John 5 Comments

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: , , , , ,
5 Responses To This Article
  1. James Burke says:

    This looks a lot like Dojo’s declare:
    http://dojotoolkit.org/reference-guide/dojo/declare.html

    • John says:

      @James Burke:
      That’s rather interesting. I didn’t look into Dojo at all as I worked on this, but you’re right there are lots of similarities.

  2. Marcus Dalgren says:

    Hello!

    I tried out your declare function because the idea really appeals to me but when I try to use it I get the error $.setObject is not a function.
    Is this supposed to be a method native to jQuery or did you miss putting that method in the code?
    I noticed that line 113 is the only place setObject is used and it isn’t declared anywhere in the code.
    When I googled jQuery setobject I found this: http://benalman.com/code/projects/jquery-getobject/docs/files/jquery-ba-getobject-js.html am I supposed to load that first?

    Any help would be very appreciated since I think this is an awesome idea and I also dig the PHP like syntax.

    Kindly,
    Marcus

    • John says:

      @Marcus Dalgren:
      Yes, my apologies, I neglected to mention I was using the jquery-getobject plugin. I had originally written my own setObject but why create & maintain something already in existence?

      I’ll update the post to reflect the inclusion of jquery-getobject.

      Thanks.

  3. WP Themes says:

    Nice brief and this post helped me alot in my college assignement. Thank you as your information.

Leave a Response