Higher-Order Functions in Javascript

Higher-Order Functions in Javascript
Spread the love

Functions are a fundamental building block of JavaScript. They can be used to perform calculations, manipulate data, and control the flow of execution. In JavaScript, functions are also first-class citizens, which means that they can be treated like any other value.

First-class citizen in a programming language means that an entity (such as a function) has the same properties and abilities as other entities. This means that functions can be:

  • Assigned to variables
  • Passed as arguments to other functions
  • Returned from functions
  • Stored in data structures

Higher-order functions are functions that take other functions as arguments or return functions as values. They are a powerful tool that can be used to write more concise, reusable, and maintainable code.

In this article, we will explore the following topics:

  • What are higher-order functions?
  • Why are higher-order functions useful?
  • How to use higher-order functions in JavaScript
  • Examples of higher-order functions in JavaScript

By the end of this article, you will have a good understanding of what higher-order functions are and how to use them in JavaScript.

First off, let’s start with touching lines with the first class feature of Javascript

1). Javascript functions are treated as values that can be assigned to variables as seen in the example below:



let withdraw =  function(amount)=>{

  let fee = 10;

  let processWithdrawal = amount - fee;

  return processWithdrawal;

}

In the piece of code above, you’ll notice that the anonymous function is being assigned to the variable withdraw. This is only possible in programming languages that supports first-class functions like Javascript, Haskell, F#, Scala, etc.

2). Also, first-class function languages allows you to pass functions as arguments to other functions like so:



const calculate = function(array, func){

  let output = [];

   for(let index=0; index<array.length; index++){

     output.push(func(array[index]));

   }

   return output;

}

In the function above, we passed in an array and a callback function to the anonymous function and inside the anonymous function, we iterate through the array and passed the value of the array to the callback function func to manipulate before pushing to the variable output.

3). Finally, a first-class function allows a function to return another function. Here is an example;



function speak(animal) {

  return function(sound) 

    return `${animal} ${sound}`;

  };

}

let dog = speak("Dog");

let dogSound = dog("barks");

console.log(dogSound);// Dog barks

As seen above, the function speak returns an anonymous function that returns the argument passed to them.

Categorically, a function that accepts functions as arguments or a function that returns a function is referred to as a Higher Order Function in Javascript. We’ve seen examples of such functions in the last two sample code.

There are lots of Higher-Order Functions (HOF) implementation in native Javascript such as

  • Array.map
  • Array.filter
  • Array.includes
  • Array.reduce.

If you’ve noticed, the calculate function example behaves like map except that we didn’t add it to the Array prototype object. If we do add it as shown below, it should look and behave exactly same way.

//

Array.prototype.calculate = function(func) {

  let output = []

  for (let i = 0; i < this.length; i++) {

    output.push(func(this[i]))

  }

  return output;

}

const sum = [1, 2, 3, 4, 5].calculate((item) => {

  return item + 2;

});

const multiply = [1, 2, 3, 4, 5].calculate((item) => {

  return item * 2;

});

const divide = [1, 2, 3, 4, 5].calculate((item) => {

  return item /2 ;

});

But what are the benefits of a higher order function and when should you use them? 

Simply put;

  • Higher-order functions can help to make code more concise and reusable.
  • They can also help to make code more modular and easier to test.
  • By abstracting away the details of how a task is performed, higher-order functions can make code more maintainable and flexible.

When you want your code to be re-useable

Why would you want to write a function in the first place? It’s a no brainer, right?  

You want to make your code modular, achieve single-responsibility principle (SRP), DRY,  and re-useable — or maybe you have other reasons. Higher-order functions take it a little further to make it flexible for you to achieve all of these.

For example if we want to achieve the calculate function above without higher order function, we’ll have to write a lot of repeated functions for each arithmetic operation we want to perform. It should look like this:

const add = (array)=>{

  let output = [];

    for (let i = 0; i < array.length; i++) {

       output.push(array[i]+2)

  }

  return output;

}

const multiply = (array)=>{

  let output = [];

    for (let i = 0; i < array.length; i++) {

       output.push(array[i]*2)

  }

  return output;

}

const divide = (array)=>{

  let output = [];

    for (let i = 0; i < array.length; i++) {

       output.push(array[i]/2)

  }

  return output;

}

console.log(add([1, 2, 3, 4, 5]))

console.log(multiply([1, 2, 3, 4, 5]))

console.log(divide([1, 2, 3, 4, 5]))

One thing that is common in the functions above. We are repeating the code and changing only the arithmetic operation.

The truth is, you don’t have to write HOF all the time, it doesn’t mean that your code is bad if you don’t use HOF. If your function doesn’t need to be re-used or is pure, there is no point, when you find yourself repeating same code with very little changes to the code you should consider making it a higher-order function.

Behavioural Abstraction

Another very important use case for Higher Order Functions is behavioural abstraction. First order functions allows you to store behaviours that act on data alone. However, high order functions goes further to allow you abstract the functions that act on other functions and consequently act on the data in them.

In our previous example, we showed how a function can return another function. This behaviour can be easily regarded as behavioural abstraction since we can completely abstract the behaviour of the function from the behaviour of the returned function. This concept is also seen in the part where a function accepts another function as an argument as it allows us to influence the behaviour of the more generic function to create our own custom behaviour without modifying the function itself. 

function speak(animal) {

  return function(sound) {

    return `${animal} ${sound}`;

  };

}

The anonymous function inside the speak function is a different behaviour, we can delegate some actions we don’t want to implement in the host function speak to the returned function. And when we pass another function, it allows us to compose smaller functions that depend on the more generic function.

Generic Array Manipulations

Another area Higher Order Functions shine is when working with arrays. Often times there are very repetitive tasks that developers do that could easily be created ones and extended based on the need. Some of this data manipulation methods are now supported natively in Javascript. The common ones are filter , map, and reduce. 

Assuming you are given an array of dogs and you are required to filter the dogs whose first 3 letters of their name starts with bar and feed them. The end goal here is to feed the dogs. We could easily use the native filter array prototype to select the dogs with these characteristic and implement the feeding mechanism.

Here is a sample code demonstrating this scenario:

let dogs = ["barry", "banga", "barlong", "damilo"];

const dogsToFeed = dogs.filter((dog) =>dog.substr(0, 3) == "bar");

const feedDog = (dogsToFeed) => {

  dogsToFeed.forEach((dog) => {

    console.log("Feeding", dog)

  });

}

feedDog(dogsToFeed)

In this example we also introduced .forEach higher order function as well. It accepts a function as an argument and allows you to manipulate the function elements as you desire without returning anything.

Summation/Combination using Reduce

Reduce is one of the Javascript native higher order function that allows you to compute a single value from an array. 

For example, assuming we want to get the total of all the values in an array from 1 to 10 we can easily use a reducer HOF to get an answer quickly.

const total = [1,2,3,4,5,6,7,8,9,10].reduce((accumulator, currentValue)=>accumulator+currentValue);

console.log(total); //55

Hold on, we can even do more. Assuming we have a more complex problem like merging  multiple arrays together to form one array. If you have only two arrays, you could easily use the concat method and that will be okay. But with multiple arrays like this [[1,2], [3,4,5], [2,54,6,57,7], [3,4,34,3]] it can be a little tricky.  But we could fix that with the reduce HOF. 

Here is a sample:

let collections = [[1,2], [3,4,5], [2,54,6,57,7], [3,4,34,3]]

const collection =  collections.reduce((accumulator, currentValue)=>{

  return accumulator.concat(currentValue)

});

console.log(collection) //[1, 2,  3, 4, 5, 2,54, 6, 57, 7, 3, 4,34, 3]

You’ll notice that without changing the reduce function source code we’ve been able to use it to create two different behaviours and solve two different problems.

Summing it all up

The cases where you’ll likely want to use a higher order function are endless. Higher order functions are a very useful tool to help you develop more functional, testable, re-useable, and maintainable code.

Buy Me A Coffee

Published by Eze Sunday Eze

Hi, welcome to my blog. I am Software Engineer and Technical Writer. And in this blog, I focus on sharing my views on the tech and tools I use. If you love my content and wish to stay in the loop, then by all means share this page and bookmark this website.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.