Javascript can be a mindfuck

20 Dec 2010

This is the ‘_bindAll’ method from underscore.js.

// Bind all of an object's methods to that object. Useful for ensuring that
// all callbacks defined on an object belong to it.
_.bindAll = function(obj) {
    var funcs = slice.call(arguments, 1);
    if (funcs.length == 0) funcs = _.functions(obj);
    each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
    return obj;
};

It’s great for binding variables like this to callback functions (like keypresses, mouseovers, etc..). In Javascript callbacks, the variable ‘this’ is usually assigned to the window object, which may not be as convenient as some custom object that has more context.

There’s some good examples of bindall in the underscore test file.

Another cool example of Javascript in underscore is the template function. This function parses erb-style templates in Javascript:

_.templateSettings = {
  evaluate    : /<%([\s\S]+?)%>/g,
  interpolate : /<%=([\s\S]+?)%>/g
};

_.template = function(str, data) {
  var c  = _.templateSettings;
  var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
    'with(obj||{}){__p.push(\'' +
    str.replace(/\\/g, '\\\\')
       .replace(/'/g, "\\'")
       .replace(c.interpolate, function(match, code) {
         return "'," + code.replace(/\\'/g, "'") + ",'";
       })
       .replace(c.evaluate || null, function(match, code) {
         return "');" + code.replace(/\\'/g, "'")
                            .replace(/[\r\n\t]/g, ' ') + "__p.push('";
       })
       .replace(/\r/g, '\\r')
       .replace(/\n/g, '\\n')
       .replace(/\t/g, '\\t')
       + "');}return __p.join('');";
  var func = new Function('obj', tmpl);
  return data ? func(data) : func;
};

To see how this works, it helps to see exactly what kind of output is returned by this function. If you pass a simple template to the function

The start of the template
<%= "Let's add some interpolated strings" %>
<%= 12+12 %>
The end of the template

You get the following returned:

function anonymous(obj) {
  var __p=[],
  print=function() {
    __p.push.apply(__p,arguments);};
    with(obj||{}){
      __p.push( 'The start of the template', 
                "Let's add some interpolated strings" ,
                '', 
                12+12 ,
                'The end of the template');
    }
  return __p.join('');
}

This makes it pretty clear what the template function does - it slices the template string and concatenates all the parts into an array, and wraps it all in a function body. Nifty.