Prototype’s Function#wrap

So Prototype 1.6 has finally hit the streets, and it looks to be a very nice piece of work. Though I’m no longer using Prototype at my day job (we use YUI, plus a layer of syntactic sugar that I’m in the process of writing), I’m interested to see what is possible with the JavaScript language. And, as it’s MIT-licensed, I can still borrow features from Prototype and weave them into my development toolkit.

A while back I wrote about how to intercept calls to JavaScript functions so you can alter their behaviour. Turns out that my method was unbelievably ugly and unnecessarily verbose: I was ignoring JavaScript’s functional capabilities and relying too much on copying techniques from Ruby. Let’s take a look at Prototype’s Function#wrap:

Function.prototype.wrap = function(wrapper) {
  var __method = this;
  return function() {
    return wrapper.apply(this,
        [__method.bind(this)].concat($A(arguments)));
  }
}

It’s quite simple really: it stores a reference to the function being wrapped (__method), and returns a new function (beginning at line 3), which I’ll refer to as foo. When foo is called, it creates a copy of __method bound to foo’s scope (__method.bind(this)). It then calls wrapper in the scope of foo (wrapper.apply(this ...)), with the bound __method inserted as the first argument. It’s easier to see what’s going on here with some examples:

var add = function(a, b) { return a + b; };

There we have a very simple adding function with two arguments. Using wrap, we can overwrite this function and change its behaviour:

add = add.wrap(function(originalAdd, a, b) {
  return 2 * originalAdd(a, b);
});

add will still take two arguments, but wrap has inserted some magic behind the scenes to gives us a reference to originalAdd. After this wrapping operation, add(3, 5) returns 16 rather than 8. We could have done whatever we want with originalAdd inside our wrapper function to change the output of add – we could even choose not to call originalAdd at all and disable add for good.

Because of the way that wrap deals with execution scope, it works with object methods that use the this keyword:

var whizzBang = {
  name: 'Fizz buzz',
  
  say: function(thing) {
    alert(this.name + ' likes ' + thing);
  }
};

whizzBang.say('apples');
// --> alerts "Fizz buzz likes apples"

We can wrap whizzBang.say and all our uses of the this keyword will refer to the proper thing:

whizzBang.say = whizzBang.say.wrap(function(originalSay, stuff) {
  originalSay(stuff);
  if (!this.n) this.n = 0;
  alert(this.name + ' has ' + this.n + ' ' + stuff);
  this.n++;
});

whizzBang.say('oranges');
// --> alerts "Fizz buzz likes oranges", then "Fizz buzz has 0 oranges"


whizzBang.say('kiwis');
// --> alerts "Fizz buzz likes kiwis", then "Fizz buzz has 1 kiwis"

So this, both in originalSay and inside the wrapper function, refers to whizzBang, just as (I hope) you’d expect it to. This is really useful, and is actually how Prototype implements method inheritance with $super inside Class.create.