Thoughts from the office by Ed Ball
Wednesday, March 30, 2005

As I mentioned before, for...in would be a convenient way to enumerate arrays, if it actually worked the way you’d expect it to. Fortunately, anonymous functions make it possible to write a ForEach method that’s almost as easy to use as a proper foreach.

function ForEach(array, fn)
{
  for (var n = 0; n < array.length; n++)
    fn(array[n]);
}

As you can see, ForEach calls the specified function for each element of the array. So if you’d like to calculate the sum of the items in an array:

function SumArray(array)
{
  var nSum = 0;
  ForEach(array, function (n) { nSum += n; });
  return nSum;
}

I hope you agree that this syntax is not only more compact than an explicit loop, but that it is also easier to understand. It is also less efficient, of course, but not significantly so, except in special cases where you would certainly measure to determine its impact; you could easily unravel it into a standard loop if necessary.

My preferred implementation of ForEach is slightly more complicated.

function ForEach(array, fn, objThis)
{
  objThis = objThis || this;
  var len = array.length;
  for (var n = 0; n < len; n++)
  {
    var r = fn.call(objThis, array[n], n);
    if (r !== undefined)
      return r;
  }
}

Let me explain:

  • Calculating the array length outside the loop can be slightly faster.
  • I pass the item index as the second argument to the called function, since that can often be useful. (The called function doesn’t need to declare it as an explicit argument if it doesn’t need it.)
  • If the called function returns anything but undefined, the loop is terminated. This allows us to simulate what would be a break statement in a normal for loop. The ForEach method returns the value returned by the called function.
  • If the called function returns undefined, the loop continues. (A function returns undefined if it gets to the end without hitting a return statement, if it hits a return statement without an expression, or if it hits a return undefined (naturally).) An explicit return statement without an expression thus simulates what would be a continue statement in a normal for loop.
  • Using the call method allows us to specify this for the called function, which can be useful when calling ForEach from an object method.
  • The objThis argument is optional, but I make sure that it is never undefined, because under Internet Explorer, the call method is slightly faster when the first argument is a valid object (passing undefined forces it to determine the “global” object itself).

For example, we can use ForEach to find an item in an array, returning the index of that item, or -1 if the item is not found.

function FindInArray(array, find)
{
  var index = ForEach(array,
    function (item, n)
    {
      if (item == find)
        return n;
    });
  return index === undefined ? -1 : index;
}

In many cases (but not all) I have found that using ForEach is easier to write and more natural than an explicit loop. YMMV.

Update: Explicitly described how to simulate a continue; thanks to Eli for his comment.

3/30/2005 9:53:48 AM (Pacific Daylight Time, UTC-07:00) | Comments [3] | Code | JavaScript#
3/30/2005 12:06:13 PM (Pacific Daylight Time, UTC-07:00)
Ed Ball, did you ever know that you're my hero? You're everything, everything I wish that I could be.

I've been using a previous incarnation of your ForEach technique where when the passed function returns false, the loop ends (simulating *break*), and if the passed function returns true, the loop just goes on to the next item (simulating *continue*). Thus, one would *return true* at any point of the function body to continue the loop.

In this new and improved version of the technique, I take it that one would just *return* (i.e., return undefined) to simulate the *continue* ? (You don't explicitly say.)

With this version, there is the added benefit that the loop-body function can return something meaningful ... very interesting ...
7/10/2005 6:47:47 PM (Pacific Daylight Time, UTC-07:00)
I was just wondering something about the pre-existing for() language construct. I know that I'm a relative newcomer when it comes to grasping the "hidden depths" of JavaScript (or its proprietary cousin, of course ;) -- but what it is that allows individual statements/expressions to be supplied as "arguments", in these special looping cases? The fact that semicolons are present has always left me wondering why it is this way; although I do know that they need to be present because they are direct statements, if you know what I mean.

The way in which you used a function as an argument to ForEach() has raised the issue once again -- could this ever be possible from a user-defined angle, or is it strictly limited to the runtime engine preset constructs, as I would assume? Could you ever supply such a similar direct statement or expression, without having to use a function as such?
trojjer
7/10/2005 7:49:25 PM (Pacific Daylight Time, UTC-07:00)
(Please reply to trojjer@gmail.com if you want to, for that last comment.)
trojjer
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):

Search
Archive
Links
Categories
Administration
Blogroll