Normally, a call to a method causes the JavaScript interpreter to set the this reference to point to the object of which the method is a member. This means, for example, that calls to global functions set the this reference to point to the Global object (the 'window' object where a web browser is the platform).
Similarly, calls to a non-global function, such as:
MyObj.func_A ();
...will cause the interpreter to set the this reference to point to MyObj. In the case of constructor calls, the interpreter sets the this reference to point to the object that is created by the action of the 'new' operator.
However, JavaScript functions are 'first-class' objects, and provide two methods called call and apply. These (very similar) functions allow a method's caller to specify explicitly what the this reference should point to.
For example:
MyObj.func_A.call (YourObj);
...Will cause func_A to execute as if it were a member of YourObj (the this reference will refer to YourObj).
AspectJS is completely transparent from the viewpoint of interceptees and their callers, therefore the call and apply methods operate as usual. In Example 57, a prefix is applied to myFunc, which is then invoked using the call method of the underlying function-object, wherein the this reference is set to point at YourObj. At this point the prefix executes, followed by myFunc, which now has access to YourObj's member (and methods, if it had any).
Consult a good JavaScript tutorial/reference for more information on the call and apply methods of function objects.
// Example 57
var YourObj =
{
Name : "Barney"
};
function prefixFunc () { alert ("prefixFunc executed"); }
function myFunc () { alert ("myFunc executed - Name = " + this.Name); }
AJS.addPrefix (this, "myFunc", prefixFunc);
myFunc.call (YourObj);
--------------------------------------
Output:
prefixFunc executed
myFunc executed - Name = Barney
Note that replacing an object's reference to one of its methods with a reference to another function will cause that other function to execute when the method is invoked (which is the very principle that AspectJS exploits). This means that to replace an intercepted method with a reference to another function is to disable execution of all the replaced-function's affixes as well.
If the replacer keeps a copy of the reference to the replaced method then the interceptee can still be invoked by applying parentheses to the object holding the copy, and doing so will cause its affixes to execute as well.
It follows that if a method of a given object is replaced with a reference to an intercepted function then that function will continue to operate in an intercepted manner when it is invoked through its new owner. Given this, it follows that restoring an intercepted function to its original owner will preserve the operation of any affixes it has, which will execute as normal on invocation of the interceptee.
Example 58 illustrates these points.
// Example 58
function prefixFunc () { alert ("prefixFunc executed"); }
var MyObj =
{
method : function () { alert ("method executed"); }
};
AJS.addPrefix (MyObj, "method", prefixFunc);
MyObj.method ();
var methodRef = MyObj.method;
MyObj.method = function () { alert ("Replacement function executed"); }
MyObj.method ();
methodRef ();
MyObj.method = methodRef;
MyObj.method ();
--------------------------------------
Output:
prefixFunc executed
method executed
Replacement function executed
prefixFunc executed
method executed
prefixFunc executed
method executed
Inner functions cannot be intercepted directly because they are not 'members' of an object (despite the fact that functions are first-class objects in JavaScript)
However, they can be intercepted if they are members of an object that is defined within a function, and Example 59 illustrates this.
// Example 59
function prefixFunc () { alert ("prefixFunc executed"); }
function myOuter ()
{
var myObj =
{
innerMethod : function ()
{
alert ("innerMethod executed");
}
}
AJS.addPrefix (myObj, "innerMethod", prefixFunc);
myObj.innerMethod ();
}
myOuter ();
--------------------------------------
Output:
prefixFunc executed
innerMethod executed
While inner functions cannot be intercepted directly, they can act as affixes, and Example 60 shows this.
// Example 60
function outerFunc ()
{
function innerFunc ()
{
alert ("innerFunc executed");
}
var InnerObj =
{
method : function () { alert ("method executed"); }
};
AJS.addPrefix (InnerObj, "method", innerFunc);
InnerObj.method ();
}
outerFunc ();
--------------------------------------
Output:
innerFunc executed
method executed
If an inner function can be used as an affix, it follows that any reference to an inner function that is returned by another function can be used as an affix. In this case, however, calling a function in order to retrieve the reference creates a closure, which the affix function has access to when it executes.
Example 61 demonstrates this.
Using a closure as part of a prefix or suffix means that all of the benefits of closures can be exploited in conjunction with method-call interception, with all the powerful design implications therein.
// Example 61
function getInner (Value)
{
function innerFunc () { alert ("innerFunc executed - Value = " + Value); }
return innerFunc;
}
function myFunc () { alert ("myFunc executed"); }
function yourFunc () { alert ("yourFunc executed"); }
AJS.addPrefix (this, "myFunc", getInner (10));
AJS.addPrefix (this, "yourFunc", getInner (20));
myFunc ();
yourFunc ();
--------------------------------------
Output:
innerFunc executed - Value = 10
myFunc executed
innerFunc executed - Value = 20
yourFunc executed
JavaScript allows the enumeration of an object's methods and attributes using a for .. in loop. When doing this in conjunction with using AspectJS, however, do remember that, if a method has been intercepted, its 'value' will not be what it is normally. Example 62 illustrates this.
Here the members of MyObj are enumerated, after which a prefix is applied to its second method. Enumerating the object's members again then gives the contents of the interception mechanism set up by AspectJS (although, for the purposes of clarity, this has been replaced in the example's output listing with a comment).
The important point is that while such a change may be fairly obvious in this example, it holds the potential for great confusion with production code.
// Example 62
function prefixFunc () { alert ("prefixFunc executed"); }
var MyObj =
{
Name_1 : "Fred",
Name_2 : "Barney",
method_1 : function () { alert (this.Name_1); },
method_2 : function () { alert (this.Name_2); }
};
for (var Property in MyObj) { alert (Property + ": " + MyObj[Property]); }
AJS.addPrefix (MyObj, "method_2", prefixFunc);
for (var Property in MyObj) { alert (Property + ": " + MyObj[Property]); }
--------------------------------------
Output:
Name_1: Fred
Name_2: Barney
method_1: function () { alert (this.Name_1); }
method_2: function () { alert (this.Name_2); }
Name_1: Fred
Name_2: Barney
method_1: function () { alert (this.Name_1); }
method_2: function () { /* Contents of interception proxy-function */ }
Note that, when working with a debugger, the details that it gives of function call-chains may include the names of two functions that are internal to the AJS object. These are 'AJSProxy' and 'AJSExecutor', and an example of the way these arise in Firebug v1.2.1 can be seen in the first screen-shot.
Note that their names been chosen in an attempt to ameliorate any confusion that they may cause developers when they appear in a debugger (they could be considerably more terse and obscure).
Note also that multiple prefixes and suffixes can also cause AJSExecutor to be repeated many times in a call-chain display. This is shown in the second Firebug screen-shot.
If you run the code for the AJS object through JSLint it reports that a number of objects referred to in the code are 'implied globals'. In fact, there is nothing wrong with the code, as the purported offenders are ensconced firmly within the scope of the functions that use them (absolutely no part of AspectJS object uses global objects), but JSLint cannot see this.
Secondly, if running the AJS object-definition through JSLint, do remember to enable the 'Tolerate Sloppy Line-breaking' option - without that, JSLint is a little sniffy about the rigorous layout-pattern used in that code body. Do note, however, that this issue disapppears naturally if you minify the code concerned (see below).
Note also that these two caveats should not be taken as a criticism of JSLint - it is a superb tool.
AspectJS is not supplied in 'minified' form (ie. without redundant white-space), nor is it available in obfuscated (or 'scrunched') form, where all internal objects and functions are renamed using (ideally) single alphabetic-characters. Nor is it available in 'gzipped' form (modern browsers will accept scripts as .gz files).
Given this, and if you are seeking minimal overhead in your applications (and good on you for that), then minification/scrunching/GZipping is a process that you will have to undertake yourself. If in a quandary about this, do note that experiments with various tools indicate that scrunching gives an average size-reduction of around 55%, and that GZipping a scrunched version yields a total size-reduction of around 90%.
These figures are impressive and make the trouble of a scrunching/compression deployment-phase worth considering. Do note, however, that obfuscators/scrunchers can introduce bugs into a given script.
As bit of general JavaScript advice, the best way is to pass your code through JSLint, fix all problems, minify the code, then run through JSLint a second time. If no problems occur then scrunch the code and pass it through JSLint a final time before deploying it.
Increasingly, JavaScript implementations are appearing on non-browser platforms. No part of the AspectJS core technology (AJS, aJS_DbC and AJS_Logger) makes platform dependent calls, therefore the product will (or should) work on any system that supports ECMAScript V3.0, and not just web browsers.
If you are developing for a new or exotic system, you may wish to test that AspectJS works fully in that context. To this end, two test-libraries are provided in the distribution set that perform exhaustive tests on aJS_DbC and aJS_Logger, and two HTML files are provided that will run these tests automatically in a web browser. If you try this code out, do be sure to read the header comments in aJS_DbC_Test.js and AJS_Logger_Test.js before proceeding.
Note that these utilities perform 52,681 tests in total, and the components pass them all, which is very reassuring indeed.