Aspect Oriented JavaScript
From Trephine
| « Trephine adds support for Groovy | Bespin trepanation » |
[subscribe] Recent blog entries
- Simple prototypal inheritance new!
- Adventures in Rhino - setters and getters
- Site improvements - fighting with Disqus
- JavaScript task chaining
- JavaScript string building benchmarks
- Efficient JavaScript string building
- Alternative JavaScript worker thread API
- Implementing JavaScript worker threads
- Thread safe DOM manipulation
- Site improvements - CSS sprites
- Trephine worker threads made easy
- Pitfalls of multithreaded browser development
- Site improvements - reducing dependencies
- The unsplittability of XML
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)