ECMAScript 5 Part 2: Array Extras

Last time in our series on IE9 support for ES5, we talked about new functions like Object.create and Object.defineProperty, which help you design more reusable components. In this post, we’ll look at another set of new functions focused on something even more basic and common: loops and arrays.

Looping over arrays is one of the most common forms of program control flow, and JavaScript programs in the browser are no exception. Common tasks such as finding a node in the DOM, iterating over the records in a JSON object from the server and filtering arrays of items based on user input, all involve iterating over arrays.

ES5 adds a set of new functions to make these common tasks easier and less error-prone. We’ll look at a few examples in this post, and see how these new functions can enable a more functional or declarative style of programming.

For Each

Have you ever tried to write JavaScript code like the following?

var people = ["Bob", "Jane", "Mary", "Chris"];

for(var person in people) {
  processPerson( person );
}

If so, you’ve probably noticed that for..in loops give you the indexes of the elements in the array, not the elements themselves. Instead, JavaScript programmers usually write this as:

for(var i = 0; i < people.length; i++) {
  processPerson(people[i]);
}

This is a very common pattern and ES5 introduces a new Array function, Array.prototype.forEach to make this really easy:

people.forEach(processPerson);

Using forEach makes this code a little shorter, but there are other more important reasons why it is often a better choice. First, you don’t have to write the array iteration logic yourself, which means fewer opportunities for bugs, and in this case less chance of an accidental off-by-one error or incorrect length calculation. Second, forEach handles arrays with empty elements automatically, skipping those indexes that don’t have values. And third, putting the body of the loop into a function ensures that any variables defined will only be in scope within the loop body, reducing the risk of loop variables conflicting with other variables in your code.

The forEach function gives a name to a common pattern, and provides a convenient way to iterate over arrays. You can see some examples of forEach in action in the ES5 Tile Switch Game on the Internet Explorer Test Drive site.

Other Common Loops

There are some other common patterns of looping over arrays. Here’s one you’ve probably seen before:

var newData = []
for(var i = 0; i < data.length; i++) {
  var value = data[i];
  var newValue = processValue(value);
  newData.push(newValue);
}

Here we create a new array by transforming each value from an existing array. Again, there’s a lot of boilerplate logic here. Using the ES5 Array.prototype.map function, we can write this more simply as:

var newData = data.map(processValue)

Here’s another common pattern:

var i;
for(i = data.Length - 1; i >= 0; i--) {
  if(data[i] === searchValue) 
    break;
}

This is the sort of code that is used to search for the last index of a value in an array – in this case searching backward from the end. Using the ES5 Array.prototype.lastIndexOf function, we can express this:

var i = data.lastIndexOf(searchValue)

The common theme here is that there are a number of common patterns of looping over arrays, and the new ES5 Array functions make it easier, more efficient, and more reliable to express these.

Aggregation

Another common use of loops is to reduce an array of data into a single value. Frequent instances of this are summing a list, getting the average of a list, and turning a list into a nicely formatted string.

The example below loops through a list to compute the sum of its values:

var sum = 0;
for(int i = 0; i < data.length; i++) {
  sum = sum + data[i];
}

With the new Array.prototype.reduce: function in ES5, we no longer have to keep track of the loop index and can aggregate the operation over the array.

var sum = data.reduce(function(soFar, next) { return soFar + next; })

In this example, the function passed to reduce is called once for each element of the array, each time being passed the sum so far, as well as the next element.

The reduce function can also take an extra parameter when there is an initial value for the aggregation. This optional parameter can also be used to keep track of multiple pieces of state. The following computes the average of an array of numbers:

var result = 
  data.reduce(function(soFar, next) { 
    return { total: soFar.total + next, 
             count: soFar.count + 1   }; 
    },
    {total: 0, count: 0 }
  );

var mean = result.total / result.count;

To process the array in descending order, from last element to first element, use the Array.prototype.reduceRight function.

Another form of array aggregation is checking whether every (or at least some) element of the array satisfies a specific requirement. The Array.prototype.every and Array.prototype.some functions provide an easy way to check these conditions. An important characteristic of these aggregates is that they can short-circuit – the result might be known before all the elements of the array have been checked, and the loop will exit.

var allNamesStartWithJ = 
  data.every(function(person) { person.name[0] === 'J'; })

As with the example in previous sections, reduce, reduceRight, every, and some make writing loops to aggregate lists easier and less error prone, while also being more composable, as we’ll see in the next section.

Putting Them Together

A few of the functions described above start with an array and produce a new transformed array. This approach allows you to compose the functions together, and to write some nicely declarative code for transforming arrays of data.

Here’s an example of using the filter, map, and reduce functions together. Imagine we are building a site that processes transaction records from cash registers. These transaction records may contain comma-separated entries indicating purchases (‘P’), refunds (‘R’) or cancelled transactions (‘C’). Sample data might look like this:

var transactions = "P 130.56, C, P 12.37 , P 6.00, R 75.53, P 1.32"

We can calculate the sum of all purchases by combining some of the array functions we’ve seen:

transactions
  // Break the string into an array on commas
  .split(",")
  // Keep just the purchase transactions ('P')
  .filter(function(s) { return s.trim()[0] === 'P' })
  // Get the price associated with the purchase
  .map   (function(s) { return Number(s.trim().substring(1).trim()) })
  // Sum up the quantities of purchases
  .reduce(function(acc, v) { return acc + v; });

This style of programming is commonly referred to as functional programming and is common in languages like Lisp and Scheme, both of which influenced the original design of JavaScript. This style is also related to concepts like Language Integrated Query (LINQ) in .NET, and the Map-Reduce model for processing and generating very large datasets in distributed computing.

The New ES5 Array Functions

In total, there are nine new functions for searching and manipulating arrays in ES5, all supported in IE9:

  • Array.prototype.indexOf
  • Array.prototype.lastIndexOf
  • Array.prototype.every
  • Array.prototype.some
  • Array.prototype.forEach
  • Array.prototype.map
  • Array.prototype.filter
  • Array.prototype.reduce
  • Array.prototype.reduceRight

Each of these functions also supports additional optional parameters not shown in the examples above, which increases their flexibility. Also, these functions can be applied not just to arrays, but to any JavaScript object that has an integer-named index and a length property.

You can try these array functions out yourself in the ECMAScript 5 Arrays demo on the IE9 Test Drive site.

The functions covered in this post are just one of the ways you can use ES5 to write simpler, more reusable code. Remember, to leverage these features, the browser must be in IE9 Standards Document Mode which is the default in IE9 for a standard document type. We’re excited to see how developers will use the features of ES5 with IE9; we’d like to hear how you plan to put these features to use. We’ll continue to explore the additions to ES5 in upcoming posts. Stay tuned!

Luke Hoban

Program Manager, JavaScript


IEBlog