Tutorial

At the Coal-Face

Intercepting User-Defined Object Methods

Applying intercepts to user-defined object-methods is the same as intercepting global methods. If a method sets an intercept on another method of the object of which it is a member, then 'this' should be specified as the IOwner argument. If it sets an intercept on a method of another object, however, then the name of that object should form the IOwner argument. In Example 35, the function prefixFunc is set as a prefix on the Method function of MyObj.

Importantly, affix functions are called 'in the context of' the object in whose context the interceptee is called. In other words, the value of the 'this' reference when the affix executes is the same as when the associated interceptee is executed.

This means that affixes have access to the same object-members that the interceptee does, and in Example 35 the prefix function is therefore able to output the value of the Name member of MyObj.


 // Example 35

 function prefixFunc () { alert ("prefixFunc executed - Name = " + this.Name); }

 function Obj (Name)
    {
    this.Name   = Name;
    this.method = function () { alert ("method executed - Name = " + this.Name); }
    };

 var MyObj = new Obj ("Homer");

 AJS.addPrefix (MyObj, "method", prefixFunc);

 MyObj.method ();

 --------------------------------------

 Output:

 prefixFunc executed - Name = Homer
 method executed - Name = Homer
            

Intercepting Object-Literal Methods

Objects defined using JavaScript's object-literal syntax are no different to instances of user-defined types created using a constructor, and their methods are intercepted in the same way.

Example 36 uses the same example as before, but defines MyObj using object-literal syntax. Other than that, the code works in exactly the same way.


 // Example 36

 function prefixFunc () { alert ("prefixFunc executed"); }

 var MyObj =
    {
    Name   : "Homer",
    method : function () { alert ("method executed - Name = " + this.Name); }
    };

 AJS.addPrefix (MyObj, "method", prefixFunc);

 MyObj.method ();

 --------------------------------------

 Output:

 prefixFunc executed
 method executed - Name = Homer
            

Intercepting Built-In Type Methods

Methods of objects that are created by built-in constructors can be intercepted. In the example, an instance of the Date type is created, and a suffix is applied to the getYear method. When that function is invoked on the Date object, the suffix executes and outputs the time.


 // Example 37

 function suffixFunc ()
   {
   alert ("Time = " + this.getTime ());
   }


 var MyDate = new Date ();

 AJS.addSuffix (MyDate, "getYear", suffixFunc);

 alert ("Year = " + MyDate.getYear ());

 --------------------------------------

 Output (in Firefox, and depending,
 obviously, on when the code is run):

 Time = 1171660796592
 Year = 107
           

Intercepting User-Defined Constructors

Intercepts are particularly useful when applied to user-defined constructors as, not least, they allow parameters to be checked on entry to these functions.

Moreover, because prefixes and suffixes are called in the context of the object in which the interceptee is called (the 'this' reference has the same value), attaching a suffix to a constructor allows that suffix to examine the contents of any objects that are created. This has clear potential in terms of debugging and logging, and forms part of the DbC approach to system development.

Furthermore, intercepting user-defined constructors also offers an alternative to adding a method or attribute to a set of objects through their prototype object, and offers an alternative to the use of constructor chaining.

These concepts are demonstrated in Example 38, where the suffix reports on the contents of a PeopleObj object, and then adds some extra members.

There is, however, a fly here in the coding ointment. In JavaScript, the constructor member of each object refers to the function that constructed the object initially. However, when a constructor function is intercepted, this principle cannot be relied upon, and the results depend on the interpreter in question.

In Chrome, Firefox and Safari, the constructor member of an object generated by an intercepted constructor-function refers to the interceptee, which is as things should be because it maintains the 'obliviousness' benefit of using MCI.

However, in Internet Explorer and Opera, the constructor member refers to the AJSProxy function (the function that the AJS object puts in place in order to implement the interception mechanism).


 // Example 38

 function PeopleObj (P1, P2, P3)
    {
    this.P1 = P1;
    this.P2 = P2;
    this.P3 = P3;
    }

 function examineAndModify ()
    {
    alert (this.P1);
    alert (this.P2);
    alert (this.P3);

    this.P4 = "Homer";
    this.P5 = "Marge";
    this.P6 = "Bart";

    }

 AJS.addSuffix (this, "PeopleObj", examineAndModify);

 var Obj = new PeopleObj ("Fred", "Barney", "Wilma");

 alert (Obj.P4);
 alert (Obj.P5);
 alert (Obj.P6);

 --------------------------------------

 Output:

 Fred
 Barney
 Wilma
 Homer
 Marge
 Bart
            

Clearly, this is not an optimum situation, and Example 39 illustrates the problem.

Moreover, and sadly, there is no simple remedy for this. One might hope that, by applying a suffix to a constructor, that suffix could reset the constructor member of new objects to what they should be. This, however, does not work because, on return to the code that invoked new originally, IE's and Opera's (accursed) interpreters reset it to refer back to the AJSProxy-function...

One approach, therefore, is to reset the constructor member explicitly after the invocation of new. Yet this is no solution because it requires the explicit insertion of code into the code-body that calls the constructor in question, whereas the whole point of MCI is to be able to augment existing functionality transparently, thus leaving the augmented code unchanged.

The best solution, although it hardly resolves the problem ideally, is to wrap each invocation of new in a function, and to create objects thereon only by calling those wrappers. This permits interception of a given wrapper, while preserving correct constructor-member semantics, thus affording us all the benefits of MCI without the danger of side-effects.

Obviously, however, this is at the expense of having to implement a given wrapper in anticipation of using MCI at some point. This again runs contrary to the motivation behind using MCI, but there is no other solution, unless you can guarantee that your applications will never run on IE and Opera.

In practice, however, this may not be so troublesome to the sophisticated developer. Well-designed JavaScript applications 'privatise' object members using closures, and since closures are not created using the new operator, the constructor-member problem does not come into play here. (AspectJS, for example, uses new only to create exception objects; moreover, that occurs only in aJS_DBC and AJS_Logger.)


 // Example 39

 function PeopleObj (P1, P2, P3) { ... }

 function prefixFunc () {  }

 AJS.addPrefix (this, "PeopleObj", prefixFunc);

 var Obj = new PeopleObj ("Fred", "Barney", "Wilma");

 alert (Obj.constructor);

 --------------------------------------

 Output (on Chrome, Firefox and Safari):

 function PeopleObj (P1, P2, P3) { ... }


 Output (on Internet Explorer and Opera):

 function AJSProxy () { ... }
            

Intercepting Native/Built-in Constructor-Calls

Further to the unreliability value that constructor members hold in objects that are generated by intercepted constructors, the ability to intercept calls to the constructors of native and built-in types should not be relied upon either.

Example 40 shows what, in principle, should work completely, but in practice does not. Here the interception can be applied, and, when the prefix executes, it has access to the parameter(s) passed to the Date constructor. However, when the suffix executes it is unable to invoke the getTime method of the newly-created object.

Moreover, the Date-constructor suffix is also unable to call the methods that Date objects support, therefore it seems that the interception mechanism does not sit well with the way that JavaScript interpreters see the constructors of built-in types.

If this is a problem, then the solution is the same as that for dealing with the constructor-member issue. To wit: one can call the built-in type constructor from within a user-defined function that returns the newly-created object. That function can be intercepted thereon, but the problem again is that this approach runs contrary to the spirit of MCI, in that it should need no additional machinery to make it work.


 // Example 40

 // This does not work completely

 function prefixFunc (IArgs)
    {
    alert ("prefixFunc executed - IArgs[0] = " + IArgs[0]);
    }

 function suffixFunc ()
    {
    alert ("suffixFunc executed ");
    alert ("Time = " + getTime ());    // Fails
    }

 AJS.addWrapper (this, "Date", prefixFunc, null, Infinity, suffixFunc);

 var MyDate = new Date (1000);

 alert (MyDate.getTime ());            // Also fails

 --------------------------------------

 Output (depending on browser etc):

 prefixFunc executed - IArgs[0] = 1000;
 suffixFunc executed

 getTime is not defined
            

Intercepting Exceptions

Nevertheless, given that user-defined constructors can be intercepted, it follows that the constructors of user-defined exception types can be intercepted as well. In Example 41, a prefix is applied to an exception constructor called MyException. When the code throws a MyException the prefix executes first.

Certainly, this has no effect on the operation of the program or on the propagation of the exception. Nor does throwing an exception while another has yet to be caught have any effect, which means that prefixes and suffixes cannot replace one exception object with another (nor, it would seem, crash the interpreter).

The primary value of intercepting exception-construction would therefore seem to lie in garnering extended error-information, allowing developers to know what exceptions are generated, while they let catch clauses in the application code swallow the exception objects silently.


 // Example 41

 function prefixFunc  () { alert ("prefixFunc executed"); }

 function MyException (Message)
    {
    this.Message = Message;
    }


 AJS.addPrefix (this, "MyException", prefixFunc);

 throw new MyException ();

 --------------------------------------

 Output (depending on browser etc):

 prefixFunc executed
 uncaught exception
            

Intercepting Prototype Functions

Functions defined in a prototype object can also be intercepted, meaning that interception of a call to such a function will occur irrespective of the object on which the function is executed. Example 42 shows this.


 // Example 42

 function prefixFunc () { alert ("This object's names are:"); }

 function PeopleObj (P1, P2, P3)
    {
    this.P1 = P1;
    this.P2 = P2;
    this.P3 = P3;
    };

 PeopleObj.prototype.outputNames = function ()
    {
    alert (this.P1);
    alert (this.P2);
    alert (this.P3);
    }

 AJS.addPrefix (PeopleObj.prototype, "outputNames", prefixFunc);

 var Obj_1 = new PeopleObj ("Fred",  "Barney", "Wilma");
 var Obj_2 = new PeopleObj ("Homer", "Marge",  "Bart");

 Obj_1.outputNames ();
 Obj_2.outputNames ();

 --------------------------------------

 Output:

 This object's names are:
 Fred
 Barney
 Wilma
 This object's names are:
 Homer
 Marge
 Bart
            

The methods of built-in types can also be intercepted using their prototype objects. In Example 43 a prefix is applied to the prototype version of getYear, the result being that any call to getYear, on any Date object, causes the prefix to execute.


 // Example 43

 function prefixFunc () { alert ("Time = " + this.getTime ()); }

 AJS.addPrefix (Date.prototype, "getYear", prefixFunc);

 var MyDate   = new Date ();
 var YourDate = new Date ();

 alert ("Year = " + MyDate  .getYear ());
 alert ("Year = " + YourDate.getYear ());

 --------------------------------------

 Output (in Firefox, and depending on
 when the code is run):

 Time = 1171661557145
 Year = 107
 Time = 1171661557145
 Year = 107
           

Prototype Functions as Affixes

Prototype functions can also be used as prefixes and suffixes. In the example, every object created using the SimpsonsObj constructor has a prototype function that will output the names contained within the object in question.

The code sets a prefix on the Output method of an object created using the FlintstonesObj constructor, where the prefix function is the OutputNames prototype-method of a SimpsonsObj. It is this prefix that outputs the names of the FlintstonesObj, while FlintstonesObj.Output simply reports that Flintstones characters live in Bedrock.


 // Example 44

 function SimpsonsObj (P1, P2, P3)
    {
    this.Name_1 = P1;
    this.Name_2 = P2;
    this.Name_3 = P3;
    };

 SimpsonsObj.prototype.outputNames = function ()
    {
    alert ("The names of some famous cartoon characters are as follows:");
    alert (this.Name_1);
    alert (this.Name_2);
    alert (this.Name_3);
    }

 function FlintstonesObj (P1, P2, P3)
    {
    this.Name_1 = P1;
    this.Name_2 = P2;
    this.Name_3 = P3;

    this.outputNames = function ()
       {
       alert ("This lot live in Bedrock");
       }

    };


 var MyFlintstonesObj = new FlintstonesObj ("Fred", "Barney", "Wilma");

 AJS.addPrefix (MyFlintstonesObj, "outputNames", SimpsonsObj.prototype.outputNames);

 MyFlintstonesObj.outputNames ();

 --------------------------------------

 Output:

 The names of some famous cartoon characters are as follows:
 Fred
 Barney
 Wilma
 This lot live in Bedrock
            

Intercepting Event-Handlers

There are a number of forms of event that can be generated within a JavaScript application. These include events generated by users of the application, such as mouse movements and button-actions, but events can also be generated under program control through the use of timers, and all of these event types can be intercepted using AspectJS.

In the example, a prefix is applied to an event-handler function that is tied into mouse-clicks on a button embedded within the page. Each click on the button causes the prefix to execute first.


 <!-- Example 45 -- >

 <html>
    <head>
       <script type = "text/javascript" src = "AJS.js"></script>
       <script type = "text/javascript">

       function prefixFunc    () { alert ("prefixFunc executed");    }
       function buttonClicked () { alert ("Event-handler executed"); }

       AJS.addPrefix (this, "buttonClicked", prefixFunc);

       </script>
    </head>

    <body>

       <input type    = "Button"
              value   = "Click Me, Dear Aspectorian"
              onclick = "buttonClicked ()" />

    </body>
 </html>

 --------------------------------------

 Output:

 prefixFunc executed
 Event-handler executed
            

In example 47, a prefix is applied to a function that is to be called after 1.5 seconds. The timer is then set with the required interval, and the prefix and TimerFunc then execute in sequence repeatedly.

Note that the intercept must be applied before the timer is set, otherwise the wrong function-reference will be passed to setTimeout (or setInterval, accordingly).


 // Example 46

 function prefixFunc () { alert ("prefixFunc executed"); }
 function timerFunc  () { alert ("timerFunc executed");  }

 AJS.addPrefix (this, "timerFunc", prefixFunc);

 setTimeout    (timerFunc, 1500);

 --------------------------------------

 Output (after a 1.5-second delay):

 prefixFunc executed
 timerFunc executed
           

Go forward to Part 6 of this tutorial.
Go back to Part 4 of this tutorial.