Perfection Kills

by kangax

Exploring Javascript by example

← back 1038 words

Protolicious

Prototype.js provides a rich set of solid components for building your web applications. Since most of those components are relatively low-level, prototype allows for an easy extension of basic functionality. For the past couple of months, I’ve been using few helpers that make painful tasks less painful. These helpers/extensions are published under the name of Protolicious (MIT license). Today, we’ll go over one of the modules from the suit – Function Extensions.

Function#not

Definition:


Function.prototype.not = function() {
  var f = this;
  return function() {
    return !f.apply(f, arguments);
  }
};

This is a pretty common pattern of “inverting” return value of a function. not acts as a function “factory” – returning a function which returns “inverted” result of original one. It’s especially useful when working with iterators, since “!” operator does not invert return value but acts immediately – inverting function’s textual representation. Example:


// Find all hidden elements (style.display == 'none')
// The usual way
$$('*').findAll(function(element) { return !element.visible() });

// Using Function#not
$$('*').findAll(Element.visible.not());

Function#runOnce

Definition:


Function.prototype.runOnce = function() {
  this.apply(this, arguments);
  return this;
};

runOnce is useful when working with event handlers. All it does is execute a function and return it. Sometimes certain behavior should not only be attached to elements, but also executed at the time it was initialized. It might seem trivial to wrap a function with “(” and “)” – forcing an expression to be evaluated – but the return value is then the one of a function, not the function itself. Another way is to call a handler before or after it has been attached. That works, unless a handler is an anonymous function. Otherwise, it needs to be named. Besides these minor pet peeves, I find runOnce looking much cleaner: Example:


// the usual way
myElement.toggle(); // run handler right away
input.observe('change', myElement.toggle); // and then attach it

// new way
input.observe('change', myElement.toggle.runOnce()); // does same thing as above

Function#toDelayed & Function#toDeferred

Definition:


Function.prototype.toDelayed = function(timeout) {
  var __method = this;
  return function() {
    var args = $A(arguments);
    setTimeout(function(){ __method.apply(__method, args) }, timeout * 1000);
  }
};

Function.prototype.toDeferred = function() {
  return this.toDelayed(0.01);
};

Prototype 1.6 introduced 2 wonderful function methods – Function#delay and Function#defer. These wrappers are essentially just a syntactic sugar on top of window.setTimeout. They allow to invoke function in a specified period of time. When working with event handlers or “heavy” applications, delayed execution appears to be quite essential. I noticed that when observing certain form events (e.g. “change” or “reset”), observer function needs be deferred in order for a form element to catch up with the new changes. toDelayed/toDeferred are another pair of function factories which simply produce delayed/deferred representation of functions they were invoked on. Example:


// handler works as expected when observing "click" event
this.radioElements.invoke('observe', 'click', handler);

// form's changes (resetting to an initial state) are not yet in affect by the time handler is called.
// toDeferred solves this problem by creating a deferred function -
// the one that will execute as soon as JS interpeter becomes available (and form's state changes)
this.formElement.observe('reset', handler.toDeferred());

Function.K

Definition:


Function.K = function(k) {
  return function() {
    return k;
  }
};

Function.K differs from other helpers in a way that it’s a static method on a Function object. Its goal is to accept an expression and return a function which returns that same expression. It could be used for currying, creating generic methods or encapsulating variables in a closure. Shared closure variables are a real show-stopper for unexperienced javascripters. Let’s see what the problem is all about and how Function.K could help us: Example:


var methods = {
  foo: function() { return 'foo' },
  bar: function() { return 'bar' }
};

var uppercased = { };

for (var name in methods) {
  // "name" variable is shared among all functions that are assigned in a loop
  // all methods in "uppercased" object return uppercased value from the last iteration of the loop
  // that is usually an undesired behavior
  uppercased[name] = function() { return name.toUpperCase() };
};

uppercased.foo(); // 'BAR' (unexpected behavior)

for (var name in methods) {
  // Function.K "encloses" "name" variable into its own closure,
  // breaking undesired reference "sharing" and resulting in a "correct" behavior
  uppercased[name] = Function.K(name.toUpperCase());
}

uppercased.foo(); // 'FOO' (now works as expected)

// an alternative workaround would be to explicitly wrap function and enclose variable:
// that could certainly work, but looks a little cumbersome
for (var name in methods) {
  uppercased[name] = (function(name){ return function(){ return name.toUpperCase() } })(name);
}

Besides “fixing” closures, Function.K could be used to create generic helpers:


// creates a function which always returns "true"
Prototype.trueFunction = Function.K(true);

// creates a function which always returns "false"
Prototype.falseFunction = Function.K(false);

Function.prototype.addAdvice

Definition:


Function.prototype.addAdvice = function(advices) {
  return this.wrap(function() {
    var args = $A(arguments), proceed = args.shift();
    var a = advices, bf = a.before, ar = a.around, af = a.after;
    bf && bf.apply(proceed, args);
    ar && ar.apply(proceed, args);
    var result = proceed.apply(proceed, args);
    ar && ar.apply(proceed, result);
    af && af.apply(proceed, result);
    return result;
  });
};

addAdvice method somewhat simulates “before”, “after” and “around” advices from Aspect Oriented Programming. I find it useful mostly for logging purposes, but the scope of possible implementations is huge – profiling, error logging, augmenting functions with additional behavior based on certain conditions, and many many more. Here’s a simple example of augmenting function with “before” and “after” logging advices. Example:


function sum(){
  return $A(arguments).inject(0, function(result, value){ return result + value; });
}

sum = sum.addAdvice({
  // before callback receives arguments which original function is invoked with
  before: function() { console.log('receiving: ' + $A(arguments)) },
  // after callback receives result value of a function
  after: function() { console.log('returning: ' + $A(arguments)) }
});

sum(1,2,3);
// logs: "receiving: 1,2,3"
// logs: "returning: 6"

There’s a test suite for these helpers – if you find any of the tests failing, don’t hesitate to let me know. I hope some of you find these helpers as useful as I find them. Have fun prototyping.

Did you like this? Donations are welcome

comments powered by Disqus