ES5 .filter(), .map() & .reduce()

ES5 .filter(), .map() & .reduce()

If you have been working with Javascript for any amount of time, you’ve quickly found out that one of the main things you do with it is manipulate data. This data could be coming from an API, from other parts of your application or from user input. Now if the data you’re getting is structured like an array and you want to work with that or manipulate it, how do you do it? Personally I’ve been doing this with for-loops and later with .forEach() for years and maybe you do to. Now what if I told you that since ES5 we’ve gotten additional means to achieving our goals with arrays.

Welcome the .filter(), .map() and .reduce() methods on the prototype of the array object. These methods allow you to write wonderfully readable, concise & easily testable code. This article aims to give you a better understanding of how each of them works, what use cases they have and maybe a new tool in your toolbelt as a programmer.

.filter()

Let’s start with .filter(). When this method is applied to an array it will return a new array. This new array contains only items that, when the callback provided to filter was applied to them, returned a value that coerced to true. Now I know that’s quite a mouthful, but bear with me. 

coercion

First of all, quick refresher. Javascript has a feature called type coercion. This means if it has to compare values against each other or check for a certain type it will try to implicitly coerce, i.e. momentarily convert the provided value, into the type it is looking for. In the case of .filter() it tries to coerce the return value of each iteration of the callback to a boolean.

With coercion out of the way let’s look at an example. Say you have an array with some prices and you want to filter out all the zero value entries. You don’t want to change the original array, so you create a new one containing only non-zero values. The way I used to do this might have looked something like this with a for() loop:

No alt text provided for this image

or like this with forEach():

No alt text provided for this image

Although these get the job done, there’s a more elegant way we can do this. Queue .filter().

No alt text provided for this image

The example above takes every element in the array as an argument and simply returns it. As we already know the return value of an iteration in .filter() is implicitly coerced to a boolean. Any returned 0 coerces to false and consequently doesn’t pass the filter. Any other, non-0 value, coerces to true and does pass.

Although this example may seem a bit contrived, you can see how much more concise this code is. Beside terseness, there are other advantages. The callback in .filter() can easily be stored separately and thus be used in multiple different places. Also, because .filter() returns a new array, multiple array methods can be combined. The use cases for this become more apparent when we take a look at .map() and .reduce() later.

Before we do that however, let’s look at a more complex example of .filter()

No alt text provided for this image

Here we have an array of characters, they have different races and for this example we want to find all the characters of a specific race. The findRace callback can help us do that. findRace takes an argument r (string with the race we’re filtering for), which returns a new callback that takes an argument m (current member being iterated over), which returns a boolean (race of the current iteration equals the race we’re filtering for). Every time the races match, true is returned and that iteration is added to the new array created by .filter(), leaving us with an array filtered by race.

By abstracting the callback logic out of .filter() we gain a couple advantages:

  • We are able to easily test the logic of findRace
  • We can reuse findRace for other queries
  • My personal favorite, we’ve practically created a readable sentence. From looking at the line .filter() is on, any developer can deduce what work is being done. Working on big projects, having readable code saves time and mental bandwidth.

.map()

Let’s get on with .map(). When this method is applied to an array it will also return a new array. This array is populated with the results of the provided callback applied to each element in the original array.

Since .map() returns a new array it should only be used if you are going to use the new array. Else you are better off using .forEach() or the for-of pattern. As a rule of thumb, if you are not using the new array and/or are not returning values from the provided callback, don’t use .map().

Let’s see a simple example of .map() in practice.

No alt text provided for this image

Contrary to what we saw with .filter(), where the return value of an iteration is coerced to a boolean, the value in the new array for an iteration of .map() is what is actually being returned by the callback. You can see we actually altered the values of the original array. The new array does still have the same amount of elements as the original one though.

We can also use .map() to change the data structure of an array. Let’s look at the characters example again.

No alt text provided for this image

Let’s say we want an array with only the names of our characters. We can easily do that with .map(). It runs the provided callback over every instance of the original array and returns only the name value. Our new array is an array of strings, rather than an array of objects.

.reduce()

Reduce executes a reducer function on every item in the array, which results in a single output.

If .filter() and .map() are rings of power, .reduce() is the proverbial one ring to rule them all.

It can do everything the other two do and allows you to mutate an array in any way you need. It is the most versatile of the three, and can be used to achieve tasks the others can’t.

Let’s take a look at an example

No alt text provided for this image

A simple use for .reduce() is where you have an array with multiple values and you want to calculate the sum of all those values. .reduce() takes a callback, and an initial value. The callback provided to .reduce() is called a reducer function. In our case this is reduceTotal. The reducer function takes two arguments, the accumulator and the current value. The accumulator is whatever is returned by an iteration of .reduce(), the current value is the current array element being iterated over. When an initial value is supplied to reduce, during the first iteration the accumulator equals that value.

In our example we add the current value to the accumulator and return the result. This turns the accumulator for the next iteration into the sum of the past iterations. When reduce has finished it returns the final accumulator as the result.

To prove that .reduce() is an absolute multitool let’s emulate the behavior of .filter() and .map().

Here we see the first example from .filter(), written with .reduce() and a reducer function called filter

No alt text provided for this image

This example is like the first example from .map(). Here our reducer function is called map.

No alt text provided for this image

By providing an empty array as the initial value and returning it as the accumulator, we can push items to that array to create a new array just like .filter() and .map() would do.

Combining methods

We now know multiple ways in which we can manipulate arrays using the methods on its prototype. The real power of this knowledge becomes apparent when they are combined. Let’s look at an example: 

No alt text provided for this image

We have an inventory of items, of each item we have a certain amount, each has a value and a type. Now say you need to know the total value of all the drinks in our inventory. With the knowledge we gained we can do this. First we .filter() the inventory for all the objects with type drink. We then .map() the total value (amount * value) of each of these items to a new array. We then use .reduce() to sum up these values with a reducer,.

Besides having used all the previous methods, this example shows you how to solve a problem with a clear separation of concerns. Each of these methods does a single job, is reusable and easily testable.

Closing thoughts

In the examples shown, the callbacks used in the methods only ever took one or two arguments; the current element, or in case of .reduce() the accumulator and the current element. In reality these callbacks take two further arguments; the current index and the original array. This was occluded for the sake of brevety.

I encourage you to learn all the nuances of these Array methods by checking out the MDN documentation for each of them. .filter(), .map(), .reduce().

Although .filter(), .map() and .reduce() are the most common Array methods you will likely come across, there are more. For a full list, check out w3school’s Javascript Array Reference.

Learning these methods allows you to write readable, concise & easily testable code.

要查看或添加评论,请登录

Wouter Schaap的更多文章

  • Design freely listen closely

    Design freely listen closely

    The internet today offers web designers and developers a lot of tools and freedom to express their creativity. We have…

  • ES6 Currying

    ES6 Currying

    In programming there is a paradigm called functional programming. The topic is broad, sometimes elusive and encompasses…

    1 条评论
  • ES6 var, let & const

    ES6 var, let & const

    With the dawn of ES6 we gained two new ways of storing data to a unique address, let & const. What do they have in…

  • BEM for Lunch

    BEM for Lunch

    Being relatively (4 years) new to the wondrous world that is web development I think I was a little bit overwhelmed…

社区洞察

其他会员也浏览了