Intercepting Function References
As in many languages, JavaScript allows a reference to a given function to be taken, and for the function to be invoked through that reference
by application of the parentheses operator (which means that '()' can be regarded as a 'call this' operator).
It follows that invocations of a function through a reference can be intercepted using AspectJS, simply by providing the reference name
to the relevant AJS or AJS_HP method, and the example illustrates this.
// Example 46
function Prefix () { alert ("Prefix executed"); }
function MyFunc () { alert ("MyFunc executed"); }
var MyFuncRef = MyFunc;
AJS.AddPrefix (this, "MyFuncRef", Prefix);
MyFuncRef ();
--------------------------------------
Output:
Prefix executed
MyFunc executed
Recursive Interceptees
Intercepting a recursive function means, in the case of a prefix (and as one would expect), that every time the
inteceptee calls itself, the affix method executes first. The diagram illustrates the execution paths involved in this.
Example Forty-Seven 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 allows us to limit the depth
that a prefix can 'follow' a function into the depths of recursion.
// Example 47
function Prefix () { alert ("Prefix executed"); }
var RecursionCount = 5;
function RecursiveFunc ()
{
alert ("RecursiveFunc executed - Recursion Count = " + RecursionCount);
RecursionCount--;
if (RecursionCount > 0) RecursiveFunc ();
}
AJS.AddPrefix (this, "RecursiveFunc", Prefix, null, 3);
RecursiveFunc ();
--------------------------------------
Output:
Prefix executed
RecursiveFunc executed - Recursion Count = 5
Prefix executed
RecursiveFunc executed - Recursion Count = 4
Prefix 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 allows us to control the point at which that method detaches
from the execution thread as a recursive function returns up from the depths of self-execution. Example Forty-Eight illustrates
this.
Note that prefix and suffix functions can also recurse, although this is not explored here.
// Example 48
function Suffix () { alert ("Suffix executed"); }
var RecursionCount = 5;
function RecursiveFunc ()
{
alert ("RecursiveFunc executed - Recursion Count = " + RecursionCount);
RecursionCount--;
if (RecursionCount > 0) RecursiveFunc ();
}
AJS.AddSuffix (this, "RecursiveFunc", Suffix, 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
Suffix executed
Suffix executed
Suffix executed
Intercepting Anonymous Functions
JavaScript supports the concept of anonymous functions, whereby a nameless function-body can be assigned to a function reference, thus
allowing the function to be executed by applying the parentheses operator to the reference.
However, if the function is designed to recurse, there is no way for it to call itself conventionally, because there is no name for it to
quote before any parentheses. This is the purpose of the 'callee' member of the
arguments array that is created when a function is invoked,
which is to say that stating arguments.callee () will cause the anonymous function to invoke itself.
Unfortunately, invoking a function using arguments.callee () will not cause any affixes to execute before
or afterwards. The example demonstrates this by means of a modified form of the example used to demonstrate interception of function references,
and shows that while Prefix is executed on the initial call to the anonymous function, the recursive calls do
not cause it to execute again.
// Example 49
function Prefix () { alert ("Prefix executed"); }
var RecursionCount = 5;
var RecursiveFunc = function ()
{
alert ("RecursiveFunc executed - Recursion Count = " + RecursionCount);
RecursionCount--;
if (RecursionCount > 0) arguments.callee ();
}
AJS.AddPrefix (this, "RecursiveFunc", Prefix, null, 3);
RecursiveFunc ();
--------------------------------------
Output:
Prefix 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
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. 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.
The situation is the same with suffixes except, of course, that the interceptee executes first, and Example Fifty illustrates this.
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 SuffixSuffix () { alert ("SuffixSuffix executed"); }
function Suffix () { alert ("Suffix executed"); }
function MyFunc () { alert ("MyFunc executed"); }
AJS.AddSuffix (this, "Suffix", SuffixSuffix); // This must come first
AJS.AddSuffix (this, "MyFunc", Suffix);
MyFunc ();
--------------------------------------
Output:
MyFunc executed
Suffix executed
SuffixSuffix 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 Fifty-One illustrates this,
and is almost identical except for the fact that the initial suffix is now prefixed.
As with other exotic applications of method 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 SuffixPrefix () { alert ("SuffixPrefix executed"); }
function Suffix () { alert ("Suffix executed"); }
function MyFunc () { alert ("MyFunc executed"); }
AJS.AddPrefix (this, "Suffix", SuffixPrefix); // Must still come first
AJS.AddSuffix (this, "MyFunc", Suffix);
MyFunc ();
--------------------------------------
Output:
MyFunc executed
SuffixPrefix executed
Suffix executed
Self-Setting Interceptees
A function can intercept itself, which is to say it can set a prefix or suffix on itself, and Example Fifty-Two
illustrates this, using a simple flag to control the use of AddSuffix.
// Example 52
var SuffixApplied = false;
function Suffix () { alert ("Suffix executed"); }
function MyFunc ()
{
alert ("MyFunc executed");
if (SuffixApplied === false)
{
AJS.AddSuffix (this, "MyFunc", Suffix);
SuffixApplied = true;
}
}
MyFunc ();
MyFunc ();
--------------------------------------
Output:
MyFunc executed
MyFunc executed
Suffix executed
Self-Clearing Interceptees
An interceptee can 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 Suffix () { alert ("Suffix executed"); }
function MyFunc ()
{
alert ("MyFunc executed");
SuffixObj.Remove ();
}
var SuffixObj = AJS.AddSuffix (this, "MyFunc", Suffix);
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 Fifty-Four, 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 Fifty-Five, 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.AddSymmetricWrapper (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 control what functions they
are applied to. In Example Fifty-Six, 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.
Note that when running this example using AJS_HP, it will not be possible for Suffix to call
AddPrefix successfully (because AJS_HP allows only one prefix,
suffix or wrapper to be applied to a given function). Moreover, the exception that AddPrefix
throws will not propagate out of the suffix function, meaning that such an error could go undetected
(unless the call to AddPrefix is wrapped in a try block).
// Example 56
// Will work only in part in AJS_HP
var PrefixApplied = false;
function Prefix () { alert ("Prefix executed"); }
function Suffix ()
{
alert ("Suffix executed");
if (PrefixApplied === false)
{
AJS.AddPrefix (this, "MyFunc", Prefix);
PrefixApplied = true;
}
}
function MyFunc () { alert ("MyFunc executed"); }
AJS.AddSuffix (this, "MyFunc", Suffix);
MyFunc ();
MyFunc ();
--------------------------------------
Output:
MyFunc executed
Suffix executed
Prefix executed
MyFunc executed
Suffix executed
Self-Removing Affixes
Affixes can also remove themselves (and other affixes) from a given interceptee. In Example Fifty-Seven,
MyFunc has a suffix function that, on initial execution removes
itself from the interceptee. Subsequent calls to MyFunc cause
that method to execute alone. Note that were Suffix executed
a second or third time (at the hands of some other caller) it would again attempt to remove
itself from the interceptee. This is harmless, as a removed affix is placed into an ineffectual
'limbo' state, wherein calling its methods has no effect, however it is also redundant and
therefore inefficient.
// Example 57
var SuffixObj = null;
function Suffix ()
{
alert ("Suffix executed");
SuffixObj.Remove ();
}
function MyFunc () { alert ("MyFunc executed"); }
SuffixObj = AJS.AddSuffix (this, "MyFunc", Suffix);
MyFunc ();
MyFunc ();
--------------------------------------
Output:
MyFunc executed
Suffix executed
MyFunc executed
Go forward to
Part 10 of this tutorial.
Go back to
Part 8 of this tutorial.
Copyright © Dodeca Technologies Ltd. 2007