.map, .reduce & .filter, Oh My!

David Atchley
tandemly
Published in
7 min readMay 28, 2015

--

Part 3 of a series on Javascript Fundamentals. See the full list of posts

Making use of Javascript’s map(), reduce() and filter() Array methods can help you write more declarative, fluent style code. Rather than building up for loops and nesting to process data in lists and collections, you can take advantage of these methods to better organize your logic into building blocks of functions, and then chain them to create more readable and understandable implementations. And ES6 gives us a few more nice array methods as well, like .from, .find, .findIndex, .of and for..of loops!

Array.map

There are very common things we want to do to lists. The first one that comes to mind is cycling through the list and performing an operation on each item. You can do this with Array.forEach.

var numbers = [1,2,3,4,5,6,7,8,9,10]; 
numbers.forEach((n, index) => {
numbers[index] = n + 1;
});
// => [2,3,4,5,6,7,8,9,10,11]

Here, we’re looping through the array and adding one to each item. This approach also has the side effect that it mutates the original list of data. As good developers, we want to reduce side effects and be transparent and idempotent with our functions and processing.

So, let’s use map() to perform the same operation and leave the original list in tact.

var plusone = numbers.map((n) => n+1); 
// => numbers: [1,2,3,4,5,6,7,8,9,10]
// => plusone: [2,3,4,5,6,7,8,9,10,11]

Easy enough. No side-effects and we have a new list with exactly what we wanted.

Now, what if we then want to take just the even numbers from this result in a list?

Array.filter

We can use Array.filter() to visit each item in a list, much like map(); however, the predicate function you pass to filter() should return either true, to allow that item in the list, or false to skip it. Also like map(), filter() returns a new array with copies of the items that match the filter and does not modify the original.

var evens = plusone.filter((n) => n % 2 === 0); 
// => evens: [2,4,6,8,10]

Even easier! Now that we’ve got the list we want to work with, lets get a count of the number of how many are evenly divisible by 4.

Array.reduce

When we want to aggregate data in a list, or data related to a list, we can use Array.reduce(). reduce() applies a function to an accumulated value on each element in a list from left to right.

var byfour = evens.reduce((groups, n) => { 
let key = n % 4 == 0 ? 'yes' : 'no';
(groups[key] = groups[key] || []).push(n);
return groups;
}, {});
// => byfour: { 'yes': [4,8], 'no': [2,6,10] }

Unlike map() and filter(), however, reduce() doesn't return a new list, it returns the aggregate value directly. In the case of our number list, our accumulated value was the initial empty object passed as the last parameter to the reduce() call. We then used that to create a property based on the divisible-by-four-ness of each number that served as a bucket to put the numbers in.

Being fluent-ish

A fluent API or interface is one that provides better readability through:

  • chaining of method calls over some base context
  • defining operations via the return value of each called method
  • is self-referential, where the new context is equivalent to the last
  • terminates via return of a void context

JQuery works likes this, as well as lodash and underscore using the _.chain() method wrapper, allowing chaining of method calls on a base context that represents a DOM element tree.

Because map() and filter() return new arrays, we can take advantage of this and chain multiple array operations together.

[1,2,3,4,5,6,7,8,9,10]
.map((n) => n*2)
.filter((n) => 10 % n == 0)
.reduce((sum, n) => (sum += n), 0);
// => 12

Now, this isn’t a “real” fluent interface; but it does resemble one from a chaining perspective and gives us a more declarative approach to implementing operations on lists.

(Some) Newer ES6 Array methods

ES6 gives us some new Array methods that make some things easier compared to what we previously had to do in ES5, as well as adding some extra functionality.

Array.from()

The new .from() method is a static method of Array and allows us to create real arrays from "array-like" objects (such as arguments or dom collections); and it also allows us to pass a function to to apply to items in those arrays, giving us some .map()-like behavior as well.

For instance, we can create a real array from a DOM collection, which isn’t really an instance of Array but is array like in that it allows indexing and has a length.

var divs = document.querySelectorAll('div.pane'); 
var text = Array.from(divs, (d) => d.textContent);
console.log("div text:", text);

The above snippet takes the DOM collection returned from querySelectorAll() and uses Array.from to map across each item and return us a "real" array of the text content from those DOM elements. To use it with the arguments variable in functions is just as easy.

// Old, ES5 way to get array from arguments 
function() {
var args = [].slice.call(arguments);
//...
}
// Using ES6 Array.from
function() {
var args = Array.from(arguments);
//...
}

You can also use Array.from for other cool things, like filling in holes in arrays, since Array.from's map function is passed undefined for any empty indexes in the array-like value being operated on.

var filled = Array.from([1,,2,,3], (n) => n || 0); console.log("filled:", filled); 
// => [1,0,2,0,3]

Array.find() and Array.findIndex()

ES5 gave us Array.filter() which we've seen and is fantastic for filtering out the elements in an array. But, to find an element by value in an array in ES5, we'd have had to resort to something like the following. To loop over an array and use more complex logic to find a value, we'd have to use a for loop.

function find(list, value) { 
var index = list.indexOf(value);
return index == -1 ? undefined : list[index];
}
var arr = ['cat','dog','bat','badger','cow'];
console.log("dog? ", find(arr, 'dog'));
// dog? dog
console.log("weasle? ", find(arr, 'weasle'));
// weasle? undefined
var found;
// Find the first item longer than 3 characters
for (var i=0; i < arr.length; i++) {
if (arr[i].length > 3) {
found = arr[i]; break;
}
}
// found: 'badger'

But, ES6 allows us to do the same thing using Array.find() to get a value, if it exists in an array; and Array.findIndex() to get the index of something by value. However, instead of taking a value directly as a parameter, find() and findIndex take a predicate function that is applied to each element in the array, and once the predicate returns true, stops the search and returns the value at that position (for .find) or the index at that position (for findIndex).

// Array.find and Array.findIndex(fn) 
var found = [1,4,-5,10].find((n) => n < 0);
console.log("found:", found);
// found: -5var index = [1,4,-5,10].findIndex((n) => n < 0); console.log("index:", index);

// index: 2

Array.of()

Array.of() lets us create a new Array instance from a variable number of arguments, regardless of the type of those arguments.

var arr = Array.of(0,true, undefined, null); 
console.log("arr:", arr);
// arr: [0, true, undefined, null]

This is essentially the same as the following ES5 function:

function ArrayOf(){ return [].slice.call(arguments); }

Array.of() works more consistently at creating instances of Arrays than using the Array() constructor. The Array() constructor can run into issues if a single argument is passed and it's a number.

console.log(new Array(3, -5, 10)); 
// [3, -5, 10] - an array with the arguments as entries
console.log(new Array(3));
// [,,] - an array with three empty holes

console.log(new Array(5.7));
// RangeError: invalid array length - woops!

The for..of loop

The for..of loop creates a loop over any iterable object, including Array, arguments, Set, Map and custom iterable objects like generators. This is different than the for..in operator, as for..in iterates over an Array, you get indexes, not values. for..in can be used on Objects, but returns the property names, not values. for..of can not be used on Objects, as there is no default iterator defined for Objects in javascript.

var arr = [3,5,7,9], obj = { a: 1, b: 2, c: 3 }; // ES5 for..in over objects 
for (var p in obj) {
console.log(p);
}
// a b c
// ES5 for..in over arrays
for (var n in arr) {
console.log(n);
}
// 0 1 2 3
// ES6 for..of over arrays
for (let n of arr) {
console.log(n); '
}
// 3 5 7 9

Using for..of, we can now actually iterate values on Arrays. Though we still can't use it on Objects, we can use a generator and for..of to loop over the keys and values in an object.

// using a generator function function* entries(obj) { 
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(obj)) {
console.log(key, "->", value);
}
// a -> 1
// b -> 2
// c -> 3

Iterable objects and generators are, however, a topic for a different blog post!

If you’re looking for more information on some of the ES6 Array methods, be sure and check out Axel Rauschmayer’s Post and Hemanth HM’s post, and be sure and look at MDN’s entry on for..of.

In Part 4 in the ‘fundamentals’ series I’ll cover some of the basics of iterables and generators (a bit early for this) Objects, prototypes, delegation and composition, including Object.create().

Originally published at www.datchley.name on May 28, 2015.

--

--

David Atchley
tandemly

Founder @tandemly. Full-stack narwhal and Javascript ❤️’er. Drip coffee addict and wanna-be designer. (http://tandem.ly)