Thoughts from the office by Ed Ball
Thursday, March 31, 2005

When you start passing functions around, you often end up creating anonymous functions that call other functions with specific arguments.

For example, let’s suppose that we’re writing a DHTML calculator, and we have a function WriteDigit that writes the specified digit to the display. To set the onclick handlers of the calculator buttons, we could do something like this:

btnOne.onclick = function () { WriteDigit(1); };
btnTwo.onclick = function () { WriteDigit(2); };

That will work just fine, but there is another way. We can write a function that takes an integer as its argument and returns the anonymous function.

function BindWriteDigit(n)
{
  return function () { WriteDigit(n); };
}
btnOne.onclick = BindWriteDigit(1);
btnTwo.onclick = BindWriteDigit(2);

Why would we do such a thing? Well, one good reason is to avoid unwanted closures. Whenever you create an anonymous function, you’ve got to pay attention to the local variables that will be kept alive, particularly under Internet Explorer, as I alluded to in a previous post. In general, when you are creating anonymous functions that will outlive the function that is creating them, you should minimize the number of local variables in that surrounding function to prevent unwanted closures, accidental modification of local variables used by the anonymous function, and other surprises.

It would be a pain to write a separate binding function for every function that we want to bind; fortunately, we can write a function that takes a function as its first argument and returns an anonymous function that calls that function with its second argument.

function BindArgument(fn, arg)
{
  return function () { return fn(arg); };
}
btnOne.onclick = BindArgument(WriteDigit, 1);
btnTwo.onclick = BindArgument(WriteDigit, 2);

No need to stop with one argument, though; with a little more effort, we can write a function that will bind to any number of arguments by using the apply method.

function BindArguments(fn)
{
  var args = [];
  for (var n = 1; n < arguments.length; n++)
    args.push(arguments[n]);
  return function () { return fn.apply(this, args); };
}
btnOne.onclick = BindArguments(WriteDigit, 1);
btnTwo.onclick = BindArguments(WriteDigit, 2);

This technique comes in very handy, particularly in problems that are less contrived than this one. You can do a lot more than this with a sufficiently complex binding function; more on that in a future post.

Update: Fixed a bug in BindArguments. (Thanks, Bradley!)

3/31/2005 2:50:13 PM (Pacific Daylight Time, UTC-07:00) | Comments [8] | Code | JavaScript#
3/31/2005 6:04:28 PM (Pacific Daylight Time, UTC-07:00)
The example BindArguments function calls the supplied function immediately instead of returning a function with the arguments bound.

Perhaps the last line should be:
return function () { return fn.apply(this, args); };
?
Bradley Grainger
4/20/2006 7:09:34 AM (Pacific Daylight Time, UTC-07:00)
Is there really no other way to force Javascript to evaluate a variable in the process of building a closure instead of closing over the variable?
Benson Margulies
4/27/2007 11:09:36 AM (Pacific Daylight Time, UTC-07:00)
I'm having trouble understanding when something is a closure and when it is not.

In the BindArguments function, are the local variables args and n, not kept alive since they were accessible to the anonymous function being returned? So how is BindArguments an improvement over setting the button onclick methods to anonymous functions directly?

Thanks for taking the time to share your expertise.
Eduardo Kortright
4/27/2007 12:30:43 PM (Pacific Daylight Time, UTC-07:00)
I don't do much JavaScript any more, but I'll give it a whirl...

'args' and 'n' are kept alive, which is okay (and even necessary in the case of 'args'). But those are the only variables kept alive; when setting the anonymous functions directly, there may be other local variables that you don't want kept alive. In my first example of this post, if 'btnOne' and 'btnTwo' are local variables, each anonymous function will keep them both alive. So 'btnOne' will end up keeping 'btnTwo' alive, and vice versa. With BindArguments, this would not happen.
Ed Ball
10/14/2007 11:11:34 AM (Pacific Daylight Time, UTC-07:00)
You saved my day (i almost pulled the trigger :). Thanks a ton for sharing this information.
zichzach
12/14/2007 8:10:26 AM (Pacific Standard Time, UTC-08:00)
Thanks very much. Your examples and response to my question have been really helpful.

One note: the function BindArguments() seems to be missing an argument in the declaration. Should it be:

function BindArguments(fn, arguments)
{
var args = [];
for (var n = 1; n < arguments.length; n++)
args.push(arguments[n]);
return function () { return fn.apply(this, args); };
}

And then, if it needs to be called with more than one argument, the call for a function CalcCell(row, col) might assign the following expression to the button's onclick member:

BindArguments(CalcCell, [row, col]);

Thanks again.
Eduardo Kortright
12/14/2007 8:21:54 AM (Pacific Standard Time, UTC-08:00)
'arguments' is a special JavaScript variable that is an array of all of the arguments passed to the function.
Ed Ball
12/14/2007 9:12:53 AM (Pacific Standard Time, UTC-08:00)
Of course... Thanks again!
Eduardo Kortright
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):

Search
Archive
Links
Categories
Administration
Blogroll