Object Oriented Javascript - Part 2 | 3
[previous] |
Tidy Up
While there are only a few intrinsic classes in JavaScript, it seems a bit exessive to write a wrapper function for each method supported by each intrinsic class. A tidier solution would involve less code than that.
One solution is to provide a function that can generically generate the wrapper functions and attach them to the wrapper class prototype:
// create a proxy class to wrap another
Function.prototype.wrap = function(subClass, functions)
{
var name = subClass.getName();
// wrap sub-class' prototype functions
for ( var i in subClass.prototype )
{
if ( subClass.prototype[i] instanceof(Function) )
{
// but not instanceOf and typeOf
if ( i == 'instanceOf' ) continue;
if ( i == 'typeOf' ) continue;
this.prototype[i] = new Function("return " + name + ".prototype." +
i + ".apply(this." + name + ", arguments); " +
"this.onMethodCall(" + name + ".prototype." + i + ",arguments);");
}
}
// wrap any other user specified functions
if ( functions )
{
for ( i = 0; i < functions.length; i++ )
{
var f = functions[i];
this.prototype[f] = new Function("return " + name + ".prototype." +
f + ".apply(this." + name + ", arguments); " +
"this.onMethodCall(" + name + ".prototype." + f + ",arguments);"); }
}
// provide default onMethodCall()
if ( !this.prototype.onMethodCall )
{
this.prototype.onMethodCall = function(){};
}
}
Like the inherit function, this function is attached to the Function prototype (see below for how this is used). It takes a JavaScript sub-class and an array of function names as inputs. It then iterates through the class prototype and creates stub functions that call the intrinsic implementations. The stub functions are generated dynamically using the Function() constructor. The string argument to the constructor defines the function body which can be concatenated together with appropriate variable values and strings. The resulting functions will be formed something like this:
function()
{
return String.prototype.substr.apply(this.String, arguments);
this.onMethodCall(String.prototype.substr, arguments);
}
The stub functions make use of the apply() method (a property of the Function object). Calling apply() on a function allows the stub function to call the appropriate function on the instrinsic string instance (this.String). The second argument is the arguments collection, an array defined by JavaScript containing the arguments passed in to the stub function itself. The apply() method will use this array as the arguments to pass into function being applied. This way, the stub function can handle the arguments from the caller and pass them to the target function without knowing anything about either.
The second statement in the stub function gives the wrapper class a chance to react to method calls when they happen. The wrapped method and arguments are passed in to the onMethodCall() function. This mechanism allows an Array wrapper to maintain the length property by assigning it after each method call:
ArrayWrapper.prototype.onMethodCall = function()
{
this.length = this.Array.length;
}
An important thing to remember about intrinsic classes is that the standard or 'intrinsic' set of functions that they support are not included when the prototype is enumerated. Unfortunately, there's no way around this limitation which is why it's necessary to pass an array of function names to the wrap() function.
Wrapping an intrinsic class is now as easy as defining the wrapper class and calling the wrap() function...
// define a wrapper class for String
function StringBase(value)
{
// create String property - required by stub functions
this.String = value ? value.toString() : '';
// implement length property
this.length = this.String.length;
}
// inherit from Object
StringBase.inherit(Object);
// wrap the String class
StringBase.wrap(String,
['anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'concat',
'fixed', 'fontcolor', 'fontsize', 'fromCharCode', 'indexOf', 'italics',
'lastIndexOf', 'link', 'localeCompare', 'match', 'replace', 'search',
'slice', 'small', 'split', 'strike', 'sub', 'substr', 'substring',
'sup', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase',
'toUpperCase', 'toString', 'valueOf']);
Notice how the StringBase class assigns a property called this.String in its constructor. This property is the intrinsic sub-class instance that the StringBase instance is wrapping. The stub functions use this property to access the sub-class instance.
A word of caution: the StringBase class can be considered functionally equivalent to the instrinsic String class and would be true of a Number wrapper or Date wrapper. However, some intrinsic classes can't be wrapped so well. For example, while an Array wrapper class would support all of the methods of the Array class, it cannot support the index operator '[]' properly. Instead of returning the nth item in the array, it would only index properties of the Array wrapper instance. Unfortunately, JavaScript offers no way around this problem as there is no way to overload operators. To overcome this limitation, the Array wrapper class would need to offer item access and modifier functions:
ArrayWrapper.prototype.getAt = function(index)
{
return this.Array[index];
}
ArrayWrapper.prototype.setAt = function(index, value)
{
this.Array[index] = value;
return value;
}
Demo
Click here for a demonstration program.
Conclusion
This article has extended the inheritance mechanism presented in the previous article by providing a means to wrap JavaScript intrinsic classes. JavaScript intrinsic classes are normally optimized for the platform and browser they are running in so the inheritance mechanism will not work reliably. The wrapper classes are nearly equivalent to the intrinsic classes that they wrap, providing the same methods and properties and they have the distinct advantage in that they may be used as base classes that other classes may inherit from.
Disclaimer
While I have endeavoured to make this code as browser compatible as possible, I have only tested it with Internet Explorer (6.0), Firefox (1.5) and Netscape (8.0) as this represents of a large proportion of users.
About the Author
Guyon Roche is a freelance web developer in London, Great Britain. He specializes in Windows platforms with an interest in bridging the gaps between different technologies, visit www.silverdaggers.co.uk for more details. He can be reached via e-mail at [email protected].
[previous] |
Created:
March 27, 2003
Revised: April 28, 2006
URL: https://webreference.com/programming/javascript/gr/column19/1