Aspect Oriented JavaScript

From Trephine

Jump to: navigation, search
« Trephine adds support for Groovy Bespin trepanation »

[subscribe] Recent blog entries

Live Demos

Aspect Oriented JavaScript

Years ago, Aspect Oriented Programming got a lot of buzz, particularly with respect to statically typed, classically inherited languages like Java. JavaScript has this ability innately, with no special frameworks required.

For reference, AOP externalizes code with cross-cutting concerns (such as logging) by providing hooks into core functions. The external code attaches to these hooks, opting to alter arguments and/or execute other code before or after the invocation.

To see how this works in JavaScript, let's walk through a simple example. Consider that there is a window scope function whose signature is this:

function someInterestingFunction(a, b) {
  // mystical code
}

Our goal is to execute custom code immediately before or after someInterestingFunction(). The first thing we have to set up is a scope in which to perform our changes:

(function(){
  // variables declared in here are visible nowhere else
  // note that someInterestingFunction is accessible here
})();

In JavaScript, as opposed to statically scoped languages, all variables are scoped to the function in which they're defined (not the "block" as defined by curly braces). The above code snippet creates an anonymous function and immediately executes it. This has the effect of creating a scope in which variables can be defined, and anything in the containing scope is still accessible.

Now we can use the protection of the scope to make a reference to the original function and replace it with our own implementation.

(function(){
  var oldInterestingFunction = someInterestingFunction;
  someInterestingFunction = function() {
    // perform any "before" logic
    var result = oldInterestingFunction.apply(this, arguments);
    // perform any "after" logic
    return result;
  };
})();

As you can see, our replacement to someInterestingFunction calls the original function via its persistent handle called oldInterestingFunction. Points in the code where additional logic might make sense are marked with comments.

Along the same lines, it's trivial to add support for error handling, error logging etc:

(function(){
  var oldInterestingFunction = someInterestingFunction;
  someInterestingFunction = function() {
    // perform any "before" logic
    try {
      var result = oldInterestingFunction.apply(this, arguments);
      // perform any "after" logic
    } catch(err) {
      // perform any "error" logic
      throw err;
    }
    return result;
  };
})();

Note that this works for methods bound to instance objects and object prototypes as well, not just global scope functions. Consider a new case:

function Person(name) {
  this.name = name;
}
Person.prototype.greet = function(person) {
  return "Hello, " + person.name;  
};
 
var Alice = new Person('Alice');
var Bob = new Person('Bob');

These instance objects may be used as follows:

Alice.greet(Bob); // returns "Hello, Bob"
Bob.greet(Alice); // returns "Hello, Alice"

Now, if we wanted Bob to announce himself before greeting anyone, we could inject that logic as follows:

(function(){
  var bobsOldGreeting = Bob.greet;
  Bob.greet = function() {
    var result = bobsOldGreeting.apply(this, arguments);
    return "I'm " + this.name + ". " + result;
  };
})();

Now Bob.greet(Alice) returns "I'm Bob. Hello Alice".

Finally, if we want to change how all instance of Person greet each other, we can do that like so:

(function(){
  var oldGreet = Person.prototype.greet;
  Person.prototype.greet = function() {
    var result = oldGreet.apply(this, arguments);
    return result + ", it's nice to meet you.  I'm " + this.name;
  };
})();

Now Alice.greet(Bob) returns "Hello, Bob, nice to meet you. I'm Alice".

That's it! Hope you enjoyed the tip, I look forward to your comments.

--Jim R. Wilson (jimbojw) 12:13, 16 March 2009 (UTC)
Personal tools