loading...
Cover image for Array's Avengers: forEach(), filter(), map() and reduce()

Array's Avengers: forEach(), filter(), map() and reduce()

aasthatalwaria profile image Aastha Talwaria ・7 min read

If you are reading this, you must know what arrays are?
So, here's a quick intro of arrays.

Arrays

A special variable used to store multiple variables.
Example

//basic example of an array
var avengers = ["Iron man", "Captain America", "Thor"];

You can also store variables having different data types in an array of javascript.

Ok! So let's start with the four avengers of arrays which are:

We know these are different but we don't care how?.
Let's learn about these functions in detail.

Starting with...

1.) forEach()

forEach() works just like well known for loop which allows you to perform some action on all the elements one by one.

Syntax
array.forEach(callback(currValue , index , array), thisArg)

  • callback(mandatory) : The function which will be executed for each element. And it accepts three arguments which are:
    • currValue(optional) : aka currentValue, value on which the function is being processed.
    • index(optional) : the index of the current Value in array.
    • array(optional) : array for which forEach function is called.
  • thisArg(optional) : value of the context(this) while executing the callback function.

The 'currValue', 'index' and 'array' are optional. But, if you don't need any of these, you must be executing piece of code array.length (returns an integer equal to length of array) times.

function Calculator() {
  this.count = 0;
}
//adding 'addition' function to 'Calculator' which will take array as a parameter.
Calculator.prototype.addition= function(array) {
  /*for 'addition', 'this' will be same as of Calculator's 'this' and
 'sum' will be another attribute just like 'count'.*/
  this.sum = 0;
  array.forEach((currentValue, index, array) => {
    /* array: array which will be passed while calling the Calculator.addition function.
    index: will be index of currentValue in array */
    this.sum += currentValue;    //adding currentValue to sum
    ++this.count;
  }, this);//passing 'this', to pass the same context as Calculator to access sum and count of 'Calculator' 
}

const obj = new Calculator();
//taking a random array as an example
obj.addition([8, 12, 5]);
console.log("Count: ", obj.count);//Count: 3
console.log("Sum: ", obj.sum);//Sum: 25

In the above-mentioned example, we are calculating sum of all the elements of the array and finding the count of elements using forEach().
**you can skip the optional fields(which are index, array, currentValue and thisArg) if you do not want to use them.

2.) filter()

Unlike forEach() (just iterates over the array), filter() allows the filtering of an array based on the return type of the callback given to it. The filter() method creates an array filled with all array elements that pass a test implemented by the provided function(callback).
Yes, you are right! the filter() takes a callback as an argument whose return value decides the output.

Syntax
var result_array = array.filter(callback(currValue, index, array), thisArg)

  • callback(mandatory) : The function which will be executed for each element whose returned value will decide the output(if it returns true filter() will add the currValue to the filtered array else it will skip currValue). And it accepts three arguments which are:
    • currValue(optional) : aka currentValue, value on which the function is being processed.
    • index(optional) : the index of the current Value in array.
    • array(optional) : array for which filter() is called.
  • thisArg(optional) : value of the context(this) while executing the callback function.

Example

function Movies(){
 this.moviesCount = 0; 
 this.watchedMovies = null;
}
Movies.prototype.getWatchedMovies = function(array, watched_topic){
 this.watchedMovies = array.filter((value, index, array)=>{
  /* array: An array which will be passed while calling the 
  Movies.getWatchedMovies function.
    index: will be index of currentValue in array */
  if(value.indexOf(watched_topic) !== -1){
    ++this.moviesCount;//incrementing count when its learned
    return true; //returning true to include learned value in filtered array
  } else {
   return false;//returning false, to not include these value in filtered array
  }
 }, this);//passing 'this', to pass the same context as Movies to access moviesCount of 'Movies' 
}
let obj = new Movies();
let movies_array = ["Captain America: The First Avenger", "Captain America: Civil War", "Iron Man", "Iron Man 2"]
obj.getWatchedMovies(movies_array, "Iron Man");
console.log("Watched movies: ",obj.watchedMovies);//Watched movies: array(2) ["Iron Man", "Iron Man 2"];
console.log("Count: ", obj.moviesCount);//Count: 2

In the above-mentioned example, we filtered the movie's array using the 'watched_topic'. If we check our array(on which we applied filter) after filtering, it will not change. That means, the filter does not change or update the existing array, it gives the new filtered array every time.

Difference between forEach() and filter() is that forEach() iterates the array and executes the callback but filter executes the callback and check its return value and on basis of that return value it decided what should be put inside the filtered array (when the return value is 'true', then it adds the currValue to a final array and in case it gets 'false' filter ignores that currValue).

3.) map()

Like forEach() and filter(), map() takes a callback function and executes that callback for each element of the array.
map() returns a new array populated with the result of calling the callback on every element.

Syntax
var result_array = array.map(callback( currValue, index, array) {
// return element for result_array
}, thisArg)

  • callback(mandatory) : The function which will be executed for each element whose returned value will be added in the resulting array. And it accepts three arguments which are:
    • currValue(optional) : value on which the function is being processed.
    • index(optional) : the index of the current Value in array.
    • array(optional) : array for which map() is called.
  • thisArg(optional) : value of the context(this) while executing the callback function.

Example

var getMoviesStatus = function( movies_array, watched_topic){
/*in this example, I don't want index , movies_array and 
'this' argument inside the callback given to map(). Hence, skipping them.*/
 var moviesStatus = movies_array.map((currentValue)=>{
  if(currentValue.indexOf(watched_topic) !== -1){
   return {currentValue: "watched"};//returning 'watched' status when that movie is watched
  } else {
   return {currentValue: "pending"};//returning 'pending' status
  }
 })
 //returning the map() result, aka moviesStatus
 return moviesStatus;
}

let movies_array = ["Captain America: The First Avenger", "Captain America: Civil War", "Iron Man", "Iron Man 2"];
console.log(getMoviesStatus( movies_array, "Iron Man"));
//[{"Captain America: The First Avenger": "pending"}, {"Captain America: Civil War": "pending"}, {"Iron Man": "watched"}, {"Iron Man 2": "watched"}];

In the above example, we enhanced our previous example in which we were filtering the movies array using 'watched_topic'. But now, we are returning an array of objects having movies and their status.
Our callback is returning an object during its execution for each element having currentValue (which will be movie name in our case) and it's status. map() will take those objects and populate them in an array and will return that.
Unlike filter(), map() populates the values returned by the callback provided to it upon completion

4.) reduce()

Last but not least.
reduce() also takes the callback and executes that callback for all the elements of the array but unlike filter() and map(), it does not returns an array. It takes the reducer function (your callback), and executes it for each element and reduce the array to the single value.

Syntax
var result = array.reduce(callback( accumulator, currValue, index, array ), initialValue)

  • callback(mandatory) : The function which will be executed for each element (except for the first element, when initialValue is not provided). And it accepts following arguments which are:
    • accumulator(optional) : The accumulator accumulates the return value of callback. It is the value returned by the callback during its execution for the last iteration. For the first iteration, its value will be equal to initialValue if initialValue is provided else it will be initiated with the first element of the array for which reduce() is called.
    • currValue(optional) : value on which the function is being processed.
    • index(optional) : the index of the current Value in array. reduce() starts iteration from index = 0, when initialValue is provided. Otherwise, it starts with index = 1.
    • array(optional) : array for which reduce() is called.
  • initialValue(optional) : if initialValue is provided, the first iteration will start from index = 0 and accumulator's value(for first iteration) will be equal to initialValue. Otherwise, the first iteration will start from index = 1, and the accumulator's value(for the first iteration) will be equal to array[0]. See the example for better understanding. If the array is empty and no initialValue is provided, TypeError will be thrown. Example
//this function will calculate sum
var getSum = function (array, initialValue){
    ///callback will be passed to the reduce() 
    let callback = function(accumulator, currValue){
        return accumulator+currValue;
    }
    if(initialValue != undefined){
        //when initial value is provided passing it to the reduce
        return array.reduce(callback, initialValue);
    } else {
        return array.reduce(callback);
    }
//You can skip the if-else case by giving 0 as a default value to initialValue.
}
//calling the getSum function without initialValue
console.log(getSum([12, 8, 6, 7]));//33
//calling the getSum function with initialValue
console.log(getSum([12, 8, 6, 7], 5));//38

First of all, I apologize to Avenger's fan for not taking the avenger related example. I found this example more suitable for understanding the concept.

So coming to the point, in the above-mentioned code snippet, we have calculated the sum of the elements of the array.
In case, you provided undefined initialValue to reduce(), it will take that and will try to add elements to that. Which will give NaN at the end

  • At the first calling of the getSum function, we called it without initial value. That means, reduce() with start its iteration with index = 1 and accumulator's value will be initiated with 12(first element of provided array).
  • Whereas, while calling the getSum next time, we provided initialValue '5'. This means, this time reduce() will start its iteration with index = 0, and the accumulator's value will be initiated with 5(provided initialValue).

So, this was all about the avengers of arrays.

If you want to ask or share anything about our avengers, our array's avengers, and this post then start a discussion (in discussion box mentioned below) or you can hit me up at aastha.talwaria29@gmail.com.

And if you have any suggestions for my next article then please let me know.

Thanks for reading.

Posted on May 29 by:

Discussion

markdown guide
 

Very detailed and I love the analogy! One thing though - you are passing a this when calling forEach etc with a fat arrow function. That doesn't do anything. Passing a this is useful when using real functions or class methods/prototypes etc.

This example illustrates it, I've commented what it logs:

class MyClass {
  constructor(value) {
    this.value = value;
  }
  predicate(item) {
    return item === this.value;
  }
}

const hello = new MyClass("hello");
const goodbye = new MyClass("goodbye");
const array = ["hello", "goodbye", "goodbye", "hello", "hello"];
console.log(array.filter(hello.predicate)); // -> []
console.log(array.filter(hello.predicate, hello)); // -> ["hello", "hello", "hello"]
console.log(array.filter(goodbye.predicate)); // -> []
console.log(array.filter(goodbye.predicate, hello)); // -> ["hello", "hello", "hello"]
console.log(array.filter(goodbye.predicate, goodbye)); // -> ["goodbye", "goodbye"]
array.filter(item => {
  console.log(this); // -> undefined
  return item === "hello";
});
array.filter(item => {
  console.log(this); // -> undefined
  return item === "hello";
}, hello);

Which isn't what people often expect :)

In a fat arrow the this is always the this of the calling context.

 

Thanks for sharing your views.
Actually, I tried to use the simplest examples and focused more on explaining the parameters than actually going deep into the classes concept.

Will try to give better examples in my future articles.🙇