Intercepting a recursive function means, in the case of a prefix (and as one would expect), that every time the interceptee calls itself, the affix method executes first.
The diagram illustrates the execution paths involved in this.
Example 47 also demonstrates the principle. Here a prefix is set to execute only three times, whereas the function executes five times. The ability to limit the number of executions therefore gives control over the depth that a prefix can 'follow' a function into the depths of recursion.
// Example 47
function prefixFunc () { alert ("prefixFunc executed"); }
var RecursionCount = 5;
function recursiveFunc ()
{
alert ("recursiveFunc executed - Recursion Count = " + RecursionCount);
RecursionCount--;
if (RecursionCount > 0) recursiveFunc ();
}
AJS.addPrefix (this, "recursiveFunc", prefixFunc, null, 3);
recursiveFunc ();
--------------------------------------
Output:
prefixFunc executed
recursiveFunc executed - Recursion Count = 5
prefixFunc executed
recursiveFunc executed - Recursion Count = 4
prefixFunc executed
recursiveFunc executed - Recursion Count = 3
recursiveFunc executed - Recursion Count = 2
recursiveFunc executed - Recursion Count = 1
In the case of applying a suffix to a recursive method, the situation is a little different. Given that suffixes execute after the interceptee, a set of recursive interceptee-calls will see the suffix execute only on return from each invocation of the interceptee.
For example, if an interceptee recurses ten times, the suffix will execute ten times, but only on return from the tenth execution of the interceptee. The diagram illustrates the execution paths involved in this.
This means that limiting a suffix's execution count gives control over the point at which that method detaches from the execution thread as a recursive function returns up from the depths of self-execution. Example 48 illustrates this.
Note that prefix and suffix functions can also recurse, although this is not explored here.
// Example 48
function suffixFunc () { alert ("suffixFunc executed"); }
var RecursionCount = 5;
function recursiveFunc ()
{
alert ("recursiveFunc executed - Recursion Count = " + RecursionCount);
RecursionCount--;
if (RecursionCount > 0) recursiveFunc ();
}
AJS.addSuffix (this, "recursiveFunc", suffixFunc, null, 3);
recursiveFunc ();
--------------------------------------
Output:
recursiveFunc executed - Recursion Count = 5
recursiveFunc executed - Recursion Count = 4
recursiveFunc executed - Recursion Count = 3
recursiveFunc executed - Recursion Count = 2
recursiveFunc executed - Recursion Count = 1
suffixFunc executed
suffixFunc executed
suffixFunc executed
The arguments object that the interpreter passes implicitly to a method supports a member called 'callee' that holds the identity of the function that is executing.
This provides a way for a function to call itself by means of the following expression:
arguments.callee (...);
Unfortunately, if an intercepted method does this, then its affix(es) will fail to execute.
Example 49 demonstrates this by means of a modified form of Example 47, and shows that while prefixFunc executes on the initial call to the anonymous function, the recursive calls do not cause it to execute again.
Happily, this is no great problem, as (in this example) the function can call itself simply by invoking the 'recursiveFunc' reference rather than arguments.callee. prefixFunc will then execute prior to every execution of recursiveFunc.
// Example 49
function prefixFunc () { alert ("prefixFunc executed"); }
var RecursionCount = 5;
var recursiveFunc = function ()
{
alert ("recursiveFunc executed - Recursion Count = " + RecursionCount);
RecursionCount--;
if (RecursionCount > 0) arguments.callee ();
}
AJS.addPrefix (this, "recursiveFunc", prefixFunc);
recursiveFunc ();
--------------------------------------
Output:
prefixFunc executed
recursiveFunc executed - Recursion Count = 5
recursiveFunc executed - Recursion Count = 4
recursiveFunc executed - Recursion Count = 3
recursiveFunc executed - Recursion Count = 2
recursiveFunc executed - Recursion Count = 1
This suggests that there is no need for the callee property in JavaScript. However, it is indispensable in one specific (and somewhat extreme) case. This is when one calls a function, passing a reference to a recursive function that is defined inline within the first function's calling-arguments. The listing shows this.
Here, it might seem that the problem of using the callee property with affixed functions reappears, but this is not the case. The recursive function is not a method and therefore cannot be intercepted, therefore the issue cannot arise.
function f (funcRef) { funcRef (...); } // Define f
f ( // Call it
function (...) // Pass this function, but define it inline
{
if (...) { return; } // Stop the recursion on some condition
arguments.callee (...); // The only way to get it to recurse
}
);
Prefixes and suffixes are ordinary functions, so it follows that a function that acts as an affix to another function can itself be intercepted, and the diagram illustrates the execution paths involved.
In the case of a prefix, this means that a call to the interceptee will cause a call to the prefix, which will cause the prefix to that method to execute first, after which the original prefix will execute, followed by the original interceptee.
With suffixes, the situation is the same except, of course, that the interceptee executes first.
Given this, Example 50 demonstrates the code involved.
Note that an interception on an affix must be applied before the affix is applied to the 'root' interceptee, and this is emphasised in the listing. If the interception is applied to an affix after that affix has been applied to another interceptee then the wrong function reference will be passed to AspectJS. This is a consequence of the way that the JavaScript object-model works, rather than of the design of AspectJS.
// Example 50
function suffixFunc_Suffix () { alert ("suffixFunc_Suffix executed"); }
function suffixFunc () { alert ("suffixFunc executed"); }
function myFunc () { alert ("myFunc executed"); }
AJS.addSuffix (this, "suffixFunc", suffixFunc_Suffix); // This must come first
AJS.addSuffix (this, "myFunc", suffixFunc);
myFunc ();
--------------------------------------
Output:
myFunc executed
suffixFunc executed
suffixFunc_Suffix executed
Note that this artefact may cause problems where the calling order of the functions involved in intercepted interceptions is critical. To resolve this, change a prefix into a suffix (or vice versa), which will change the calling order. Example 51 illustrates this, and is almost identical except for the fact that the initial suffix is now prefixed.
As with other exotic applications of method-call interception, the ability to intercept interceptions (and to intercept those interceptors too, and so on) may be no more than a curiosity. It may even prove to be an appalling design decision, not least because of the potential for total confusion on the part of the developer. As with many issues in programming, just because you can do a given thing doesn't mean you should.
// Example 51
function suffixFunc_Prefix () { alert ("suffixFunc_Prefix executed"); }
function suffixFunc () { alert ("suffixFunc executed"); }
function myFunc () { alert ("myFunc executed"); }
AJS.addPrefix (this, "suffixFunc", suffixFunc_Prefix); // Must still come first
AJS.addSuffix (this, "myFunc", suffixFunc);
myFunc ();
--------------------------------------
Output:
myFunc executed
suffixFunc_Prefix executed
suffixFunc executed
A function can intercept itself, which is to say it can set a prefix or suffix on itself, and Example 52 illustrates this, using a simple flag to control the use of addSuffix.
Do note, however, that some care should be taken with self-suffixing interceptees. If the interceptee has no existing affixes then the suffix will not execute until the next time the interceptee is invoked. But if it already has at least one prefix or suffix then the new suffix will execute (along with any siblings) as soon as the interceptee returns.
// Example 52
var SuffixApplied = false;
function suffixFunc () { alert ("suffixFunc executed"); }
function myFunc ()
{
alert ("myFunc executed");
if (SuffixApplied === false)
{
AJS.addSuffix (this, "myFunc", suffixFunc);
SuffixApplied = true;
}
}
myFunc ();
myFunc ();
--------------------------------------
Output:
myFunc executed
myFunc executed
suffixFunc executed
An interceptee can also remove an affix from itself. In the example, a suffix function is applied to myFunc, which removes the suffix when it executes, meaning that the suffix function is never actually invoked.
If a prefix were applied to myFunc, that affix would execute, followed by myFunc which could remove the prefix. Subsequent invocations of myFunc would therefore not cause the prefix to execute.
// Example 53
function suffixFunc () { alert ("suffixFunc executed"); }
function myFunc ()
{
alert ("myFunc executed");
SuffixObj.remove ();
}
var SuffixObj = AJS.addSuffix (this, "myFunc", suffixFunc);
myFunc ();
--------------------------------------
Output:
myFunc executed
It is also possible for a function to act as its own affix. As with the other exotic and unusual ways of using AspectJS, this may seem strange, and may be no more than a curiosity, but it is harmless from the interception mechanism's point of view.
In Example 54, myFunc is set as its own prefix, meaning that a call to myFunc causes it to execute as the prefix, before it executes as the interceptee.
// Example 54
function myFunc () { alert ("myFunc executed"); }
AJS.addPrefix (this, "myFunc", myFunc);
myFunc ();
--------------------------------------
Output:
myFunc executed
myFunc executed
In Example 55, myFunc is set as a wrapper on itself, meaning that a call to that function causes it to execute as a prefix, then as the interceptee, before it executes a final time as the suffix.
// Example 55
function myFunc () { alert ("myFunc executed"); }
AJS.addWrapper (this, "myFunc", myFunc);
myFunc ();
--------------------------------------
Output:
myFunc executed
myFunc executed
myFunc executed
In the same way that interceptees can manipulate their own prefixes and suffixes, affixes can also manipulate other affixes, although they may not remove, promote or demote themselves or others members of the set of prefixes or suffixes of which they are a member.
In Example 56, myFunc has a suffix which, on initial execution, applies a prefix to its interceptee. A subsequent call to myFunc causes the prefix to execute, followed by myFunc and its associated suffix.
// Example 56
var PrefixApplied = false;
function prefixFunc () { alert ("prefixFunc executed"); }
function suffixFunc ()
{
alert ("suffixFunc executed");
if (PrefixApplied === false)
{
AJS.addPrefix (this, "myFunc", prefixFunc);
PrefixApplied = true;
}
}
function myFunc () { alert ("myFunc executed"); }
AJS.addSuffix (this, "myFunc", suffixFunc);
myFunc ();
myFunc ();
--------------------------------------
Output:
myFunc executed
suffixFunc executed
prefixFunc executed
myFunc executed
suffixFunc executed