Tutorial

Reflexion and Recursion

Recursive Interceptees

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.

Diagram showing execution paths through a prefixed recursive-inteceptee

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.

Diagram showing execution paths through a suffixed recursive inteceptee

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
            

Intercepting Anonymous Functions

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

       }

   );
            

Intercepting Prefixes and Suffixes

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.

Diagram showing a 'prefixed prefix', and a 'suffixed suffix'

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
            

Self-Intercepting Interceptees

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
            

Self-Affixing Interceptees

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
            

Self-Setting Affixes

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
            

Go forward to Part 7 of this tutorial.
Go back to Part 5 of this tutorial.