advised methods for Javascript with Prototype

written by sam on December 7th, 2007 @ 08:28 AM

an AOP implementation

Well, it's been a long time since including AOP is planned for Prototype (original ticket by Tobie Langel).

This is a working candidate with slight differences and improvements. Let's start with some code, as usual :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PeriodicalExecuter.addMethods(Advisable);

PeriodicalExecuter.prototype.addAroundAdvice('execute', function(proceed) {
  // assuming the existence of some benchmark function
  var duration = benchmark(proceed);
  console.log('Periodical execution took '+duration+'s');
});
// For now every single instance of PeriodicalExecuter will log the execution duration.

var greeter = Object.extend({
  congrat: function() {
    return "Congratulations";
  }
}, Advisable);

greeter.addAfterAdvice('congrat', function(returnValue) {
  return returnValue + " for Prototype 1.6, you guys made a great work."
});

greeter.congrat();
// "Congratulations for Prototype 1.6, you guys made a great work."

Differences and improvements

Among everything, Aspect is not a mixin anymore but a class that represents aspect of a given object and holds its originals methods. Advisable is the mixin containing AOP-methods that you can extend an instance with or mix into a class. What is the advantage ? well, your object won't be "polluted" with private attributes or methods anymore, but only with a single _aspect private attribute referring to the corresponding Aspect instance, created on demand.

Another feature that motived me in first place, is that you can now remove a given advice (well, only if you have a reference to it, like event handlers) and still have your method doing fine with other advices. The resulting method is computed only when advices are added or removed.

Plays nicely with prototypes and instances

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var Example = Class.create(Advisable, {
  example: function() {
    console.log('in original method');
  }
});

var ex = new Example();
ex.addAroundAdvice('example', function(proceed) {
  console.log('around, before (instance advice)');
  proceed();
  console.log('around, after (instance advice)');
});

Example.prototype.addBeforeAdvice('example', function() {
  console.log('before (prototype advice)');
});

ex.example();

// Guess what's in console ?

// around, before (instance advice)
// before (prototype advice)
// in original method
// around, after (instance advice)

Some syntaxic sugar

This prototype word is kind of annoying sometimes, and there are several cases when you just want to invoke a prototype method on directly on the class holding it.

A simple way to do it is to bind all method in Advisable to Example.prototype and then store it in our class Example.

1
2
3
4
5
6
7
8
9
Object.keys(Advisable).each(function(methodName) {
  Example[methodName] = Advisable[methodName].bind(Example.prototype);
});

// now
Example.addBeforeAdvice('example', advice);
// is equivalent to
Example.prototype.addBeforeAdvice('example', advice);
// but maybe sexier :p

This is a pattern we are using in Prototype UI in our class.js file containing extensions to Class.Methods

Download

You can check out from the svn repository to get source and unit tests.

svn co http://svn.gotfresh.info/projects/advisable/

Post a comment

Options:

Size

Colors