Have you met arguments.callee?

Just the other day I had a need to use arguments.callee and I realized that's not something you really see every day in JavaScript. Maybe I could talk about it a bit.

Anonymous functions everywhere

It's not news to anyone reading this blog that one of JavaScript's workhorses are anonymous functions. Callbacks, strategies, deferred execution, event handlers, etc. They just seem to be all over the place — and for a good reason; they can be convenient and reduce the pollution of a bunch functions that are only called from a single spot.

Another nice thing is that, once your eyes are trained to ignore the little bit of noise that they add to the code, the code is really readable and, dare I say it, expressive.

Yet another contrived example

Let's say we are really into reinventing the wheel and with our understanding of anonymous functions we create a revolutionary map function:

function map(array, compute){
  var result = [ ];
  for(var i=0; i < array.length; i++){
    result.push( compute(array[i]) );
  return result;

This function, as you can hopefully see, transforms each element of the given array into something else and returns the array of the transformed elements. Two simple uses are shown below.

//apply discount
var prices = [1, 2, 3, 4];
var discount = 0.1; // 10% off today, w00t!
var newPrices = map(prices, function(price){ return (1-discount)*price; } );
//==> [0.9, 1.8, 2.7, 3.6]

//compute areas
var squareSides = [1, 2, 3, 4];
var squareAreas = map(squareSides, function(side){ return side*side; } );
//==> [1, 4, 9, 16]

I warned you the examples would be contrived, didn't I?

Now your product manager comes and asks for a page where the users can enter a list of numbers and get the factorials for each of them. You immediately think your friend the map function will save the day. You start and...

//return the factorials
var userNumbers = [1, 2, 3, 4];
var factorials = map(userNumbers, function(number){ 
  if(number <= 1) { return 1; }
  return number * ????????(number - 1); // ooops! I need to recurse here.
} );

You see, there's this thing with anonymous functions. They don't have a name, d'oh. As I said in the beginning, we typically use them in situations where they are called only once so we can inline them and forego the need fora name. But now we're kind of wishing they had a name.

Anonymity won't hide you from me

Well, if the post tittle didn't already give it away, we can achieve that with arguments.callee. Using arguments.callee inside a function gives us a reference to the function itself. So now we can finish our code.

//return the factorials
var userNumbers = [1, 2, 3, 4];
var factorials = map(userNumbers, function(number){ 
  if(number <= 1) { return 1; }
  return number * arguments.callee(number - 1);
  //or if you were using "this" in the function you'll probably want to:
  // return number * arguments.callee.apply(this, [number - 1]);
} );
//==> [1, 2, 6, 24]

A more real world scenario

I won't leave you without at least a reference to a real use case for this feature. The example I'll show comes from Nicholas Zakas. In a blog post a while ago he showed how we can break up long running tasks with smaller timed/deferred chunks, improving the browser's responsiveness.

Here's the function from his blog post, which process chunks of an array for 50ms, then stops and call itself back to process the remaining items soon after that — giving the browser a chance to breathe and take care of its interaction with the user

//Copyright 2009 Nicholas C. Zakas. All rights reserved.
//MIT Licensed
function timedChunk(items, process, context, callback){
   var todo = items.concat();   //create a clone of the original


    var start = +new Date();

    do {
       process.call(context, todo.shift());
    } while (todo.length > 0 && (+new Date() - start < 50));

    if (todo.length > 0){
      setTimeout(arguments.callee, 25);
    } else {
  }, 25);

I hope this shows you a little new trick.

Posted 09-10-2010 6:36 PM by sergiopereira



Horses wrote re: Have you met arguments.callee?
on 09-10-2010 8:52 PM

Very interesting, but you factorial example is wrong.  "return arguments.callee(number - 1);" should be "return number * arguments.callee(number - 1);"

sergiopereira wrote re: Have you met arguments.callee?
on 09-10-2010 9:17 PM

@Horses, duh, you're right. Bad copy/paste/rename job. I've fixed it.


joseanpg wrote re: Have you met arguments.callee?
on 09-14-2010 4:52 PM

callee is a property of the arguments object, and create that object is quite expensive, because the interpreter must mount the necessary infrastructure for link formal parameters and arguments elements in each call.

It`s preferable to use a named function.

David Rivers wrote re: Have you met arguments.callee?
on 05-15-2011 7:59 PM

Alternatively, it seems you can give your function a name if you contain it by making it self-executing. E.g.:

(function ha(){return arguments.callee;})()

However, I suspect there may be side effects of this pattern.

matthew72 wrote re: Have you met arguments.callee?
on 07-20-2011 8:15 AM

what is "+new" ?

sergiopereira wrote re: Have you met arguments.callee?
on 07-20-2011 1:31 PM

@mathew, if you try that on your favorite JS console, with and without the "+", you'll see the difference.

It's just a shorthand to convert a date to its milliseconds representation.

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET


SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
NHibernate Profiler
Balsamiq Mockups
JetBrains - ReSharper
Web Sequence Diagrams
Ducksboard<-- NEW Friend!


Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers


Community Server (Commercial Edition)