JavaScript tutorial: Higher-order functions

‘Higher-order functions’ is a fancy term for functions that take other functions as arguments or that return functions. Very powerful!

JavaScript tutorial: Higher-order functions
Thinkstock

Last week, I casually dropped the term “higher-order function” when talking about memoization. While I feel comfortable throwing around terms like that now, I didn’t always know what they meant. This week we’ll examine what higher-order functions are, show some common examples, and learn how to go about creating our own.

At its core, a higher-order function is just a function that accepts a function as an argument or returns a function. This is possible in JavaScript thanks to first-class functions, which means that functions in JavaScript can be passed around like any other variable. While this sounds pretty straightforward, it doesn’t quite telegraph the kind of power you have with first-class functions.

If you write JavaScript, you have probably used higher-order functions and not even noticed. If you have ever replaced a for loop with an array method, you’ve used higher-order functions. If you have ever used the results of an AJAX call (without async/await), you’ve used higher-order functions (both promises and callbacks involve higher-order functions). If you have ever written a React component that displays a list of items, you’ve used higher-order functions. Let’s see those examples:

const items = ['a', 'b', 'c', 'd', 'e']
// Instead of this for loop....
for(let i = 0; i < items.length - 1; i++) {
  console.log(items[i]);
}

// We can use forEach, a higher-order function
// (forEach takes a function as an argument)
items.forEach((item) => console.log(item));

// Callbacks or promises, if you’re making
// asynchronous requests, you’re using
// higher-order functions
get('https://aws.random.cat/meow', (response) => {
  putImageOnScreen(response.file);
});
get('https://random.dog/woof.json').then((response) => {
  putImageOnScreen(response.file);
});

// In the React component below, map is used,
// which is a higher-order function
const myListComponent = (props) => {
  return (
    <ul>
      {props.items.map((item) => {
        return (<li key={item}>{item}</li>)
      })}
    </ul>
  );
};

Those are examples of higher-order functions that accept functions as arguments, but plenty of them return functions as well. If you’ve ever seen a function call that has two sets of parentheses, that’s a higher-order function. This sort of thing used to be less common, but if you work with Redux at all, you’ve probably used the connect function, which is a higher-order function:

export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

In the case above, we call connect with two arguments and it returns a function, which we immediately call with one argument. You may have also seen (or written) a simple logging library that uses functions as return values. In the example below, we’ll create a logger that logs its context before the message:

const createLogger = (context) => {
  return (msg) => {
    console.log(`${context}: ${msg}`);
  }
};

const log = createLogger('myFile');

log('A very important message');
// logs out "myFile: A very important message"

The example above starts to illustrate some of the power of higher-order functions (see also my previous post on memoization). Note that createLogger takes an argument that we reference in the body of the function we return. That returned function, which we assign to the variable log, can still access the context argument because it was in scope where the function was defined.

Fun fact: Referencing context is made possible by closures. I won’t go into closures here because they deserve their own post, but they can be used in conjunction with higher-order functions for some really interesting effects.

For example, using closures along with higher-order functions used to be the only way we could have “private” or tamper-proof variables in JavaScript:

let protectedObject = (function() {
  let myVar = 0;
  return {
    get: () => myVar,
    increment: () => myVar++,
  };
})();

protectedObject.get(); // returns 0
protectedObject.increment();
protectedObject.get(); // returns 1
myVar = 42; // whoops! you just created a global variable
protectedObject.get(); // still returns 1

Let’s not get carried away, though. Higher-order functions don’t require anything fancy like closures. They are simply functions that take other functions as arguments or that return functions. Full stop. If you want more examples or further reading, check out the chapter on higher-order functions in “Eloquent JavaScript” by Marijn Haverbeke.

Questions or comments? Feel free to reach out on Twitter: @freethejazz.

Copyright © 2019 IDG Communications, Inc.