Original text: jrsinclair.com/articles/20… Translated by James Sinclair: Front end little white

Higher-order functions are something that people talk about a lot, but few people explain what they are, and you probably already know what higher-order functions are. But how do we use them in the present? What are some examples of when to use them and how they can be useful? We can use them to manipulate the DOM, right? Or are those who use higher-order functions really showing off? Do they overcomplicate the code?

I think higher-order functions are useful. In fact, I think they’re one of the most important features of JavaScript as a language, but before we get into that, let’s break down what higher-order functions are. Before we understand the concept, let’s start with functions as variables.

Functions are first-class citizens

In JavaScript, we have at least three different ways to write functions. First, we can write a function declaration. Such as:

// Take a DOM element and wrap it around li
function itemise(el) {
    const li = document.createElement('li');
    li.appendChild(el);
    return li;
}
Copy the code

Hopefully you’re all familiar with that. But, you probably know that we can also write this as a function. It looks like this:

const itemise = function(el) {
    const li = document.createElement('li');
    li.appendChild(el);
    return li;
}
Copy the code

There is another way to define this function, using the arrow function:

const itemise = (el) = > {
    const li = document.createElement('li');
    li.appendChild(el);
    return li;
}
Copy the code

These three functions are essentially the same for what we want to achieve. But notice the last two, assigning a function to a variable. This may seem like nothing, but why not assign a function to a variable? But this is a very important feature, and functions in JavaScript are “first class.” That is, we can:

  • Assign a function to a variable
  • Pass a function as an argument to another function
  • Returns one function from another

Sounds good, but what does it have to do with higher order functions? Note the last two points. We’ll talk about that in a second. In the meantime, let’s look at some examples.

We’ve seen that functions can be assigned to variables, but what about passing them as arguments to other functions? We write a function, you can use the DOM elements, if we run the document. The querySelectorAll (), we will return the NodeList, rather than an array. NodeList doesn’t have a.map() method like arrays, so let’s write one:

// Apply the given function to each item in NodeList and return an array.
function elListMap(transform, list) {
    // List may be a NodeList with no.map() method, so we convert it to an array
    return [...list].map(transform);
}

// Get the span tags of all the classes in the page named 'for-listing'.
const mySpans = document.querySelectorAll('span.for-listing');

// Wrap each item in li. We use the itemise function we defined earlier
const wrappedList = elListMap(itemise, mySpans);
Copy the code

In this example, we pass the Itemise function as an argument to the elListMap function, but we can use elListMap to do more than just create a list, for example, we can use it to add a class to a list of elements.

function addSpinnerClass(el) {
    el.classList.add('spinner');
    return el;
}

// Get all the buttons with class names 'loader'
const loadButtons = document.querySelectorAll('button.loader');

// Add the spinner class name to all buttons
elListMap(addSpinnerClass, loadButtons);
Copy the code

The elListMap function takes a transform function as an argument, which means we can reuse the elListMap function for a number of different tasks.

We have now seen the example of passing functions as arguments, but what does it look like to return a function from within another function?

Let’s write a regular function. We want to get a list of

  • elements and wrap them in a
      . It’s easy:
  • function wrapWithUl(children) {
        const ul = document.createElement('ul');
        return [...children].reduce((listEl, child) = > {
            listEl.appendChild(child);
            return listEl;
        }, ul);
    }
    Copy the code

    But if we later have some paragraph elements that we want to wrap around

    , we can also write a function:
    function wrapWithDiv(children) {
        const div = document.createElement('div');
        return [...children].reduce((divEl, child) = > {
            divEl.appendChild(child);
            return divEl;
        }, div);
    }
    Copy the code

    This works fine, but the two functions look similar, the only difference being the parent element used to wrap them. Now let’s write a function that takes two arguments: the type of the parent element and the list of child elements. However, there is another way to do this. We can create a function that returns a function. It looks like this:

    function createListWrapperFunction(elementType) {
        // We return a function
        return function wrap(children) {
          // In the wrap function, we can 'see' the elementType argument
          const parent = document.createElement(elementType);
          return [...children].reduce((parentEl, child) = > {
              parentEl.appendChild(child);
              returnparentEl; }, parent); }}Copy the code

    It looks a little complicated at first, but let’s break it down a little bit. We create a function that does nothing but return another function. But the returned function remembers elementType, so when we later call the returned function, it knows what element to create, so we can create wrapWithUl and wrapWithDiv:

    const wrapWithUl  = createListWrapperFunction('ul');
    // The wrapWithUl() function now remembers that it creates a ul element
    
    const wrapWithDiv = createListWreapperFunction('div');
    The wrapWithDiv() function now remembers that it creates a div element
    Copy the code

    The returned function “remembers” something, we have a technical term for it: closures. Closures are very useful, but we don’t have to think about them too much just yet. So, we’ve seen:

    • Assign a function to a variable
    • Pass as a parameter
    • Return from one function to another

    All in all, functions are first-class citizens, which is really nice. But what does this have to do with higher order functions? Let’s look at the definition of higher-order functions.

    What is a higher-order function

    The higher-order function is:

    A function that is passed in as an argument or returned as a result

    Sound familiar? In JavaScript, functions are first-class citizens. By higher-order functions we mean functions that take advantage of this point. Nothing special, just a simple concept that sounds fancy.

    Higher-order function examples

    Once you notice, you’ll see higher order functions everywhere. The most common are functions that take functions as arguments. Let’s look at these first. Then we’ll discuss some practical examples of return functions.

    Functions that take functions as arguments use higher-order functions where callbacks are passed, which is common in front-end development. One of the most common methods is the.addeventListener () method. We can use this when we want to make our operations happen in response to events, for example, if I want a button to pop up an alert:

    function showAlert() {
      alert('Fallacies do not cease to be fallacies because they become fashions');
    }
    
    document.body.innerHTML += ``;
    
    const btn = document.querySelector('.js-alertbtn');
    
    btn.addEventListener('click', showAlert);
    Copy the code

    In the example above, we created a function that displays a warning. Then we add a button to the page. Finally we pass the showAlert() function as an argument to btn.addeventListener ()

    When we use the array iteration method, we also see higher-order functions like.map(),.filter(), and.reduce(), which we’ve seen in the elListMap() function.

    function elListMap(transform, list) {
        return [...list].map(transform);
    }
    Copy the code

    The setTimeout() and setInterval() functions both help us manage when the function is executed. For example, if we wanted to remove a display-highlighted class name after 30 seconds, we could do this:

    function removeHighlights() {
        const highlightedElements = document.querySelectorAll('.highlighted');
        elListMap(el= > el.classList.remove('highlighted'), highlightedElements);
    }
    
    setTimeout(removeHighlights, 30000);
    Copy the code

    Similarly, we create a function and pass it as an argument to another function. As you can see, the functions we use usually accept functions in JavaScript. In fact, you’ve probably already used them.

    A function that returns the result as a function

    A function that returns a result as a function is less common than a function that takes a function as an argument. But they are still useful, and one of the most useful examples is the Maybe () function, which I adapted from Reginald Braithewaite’s book JavaScript Allonge to look something like this:

    function maybe(fn)
        return function _maybe(. args) {
            // Note that the == is deliberate.
            if ((args.length === 0) || args.some(a= > (a == null)) {
                return undefined;
            }
            return fn.apply(this, args); }}Copy the code

    Instead of immediately understanding how it works, let’s go ahead and use the elListMap() function:

    function elListMap(transform, list) {
        return [...list].map(transform);
    }
    Copy the code

    What happens if we accidentally pass a null or undefined value to elListMap()? We get a TypeError, and the program crashes and stops execution. Maybe () solves this problem by using:

    const safeElListMap = maybe(elListMap);
    safeElListMap(x= > x, null);
    / / please undefined
    Copy the code

    Instead of crashing, the function returns undefined. If we pass it to another function protected by Maybe (), it will return undefined again. We can use the maybe() function to protect any function we want. This is much simpler than using an if statement.

    It is also common in the React community for one function to return another. For example, connect() in React-redux is a function that returns a function.

    Why use higher-order functions

    We’ve already seen some examples of higher-order functions. But what good have they brought us? To answer this question, let’s look at another example. The built-in.sort() method for arrays. It does have a problem. It changes the array itself instead of returning a new array, so let’s ignore it for now.

    The sort() method is a higher-order function. It takes a function as one of its arguments.

    How does it work? If we want to sort a list of numbers, we first create a comparison function. It goes something like this:

    function compareNumbers(a, b) {
        if (a === b) return 0;
        if (a > b)   return 1;
        /* else */   return - 1;
    }
    Copy the code

    To sort an array, we can do this:

    let nums = [7.3.1.5.8.9.6.4.2];
    nums.sort(compareNumbers);
    console.log(nums);
    // [1, 2, 3, 4, 5, 6, 7, 8, 9]
    Copy the code

    We can sort a list of numbers. But what does that do? How often do we have a list of numbers to sort? Very few. If there is a case for sorting, it is usually an array of objects. Something like this:

    let typeaheadMatches = [
        {
            keyword: 'bogey'.weight: 0.25.matchedChars: ['bog'],}, {keyword: 'bog'.weight: 0.5.matchedChars: ['bog'],}, {keyword: 'boggle'.weight: 0.3.matchedChars: ['bog'],}, {keyword: 'bogey'.weight: 0.25.matchedChars: ['bog'],}, {keyword: 'toboggan'.weight: 0.15.matchedChars: ['bog'],}, {keyword: 'bag'.weight: 0.1.matchedChars: ['b'.'g'],}];Copy the code

    Suppose we want to sort this array by the weight of each element. We can write a new sort function from scratch. But we don’t have to do that. Instead, we create a new comparison function.

    function compareTypeaheadResult(word1, word2) {
        return - 1 * compareNumbers(word1.weight, word2.weight);
    }
    
    typeaheadMatches.sort(compareTypeaheadResult);
    console.log(typeaheadMatches);
    / /] [{keyword: "bog,", weight: 0.5, matchedChars: [" bog, "]},...
    Copy the code

    We can write a comparison function for any type of array, and the sort() method agrees with us. Given a comparison function, it sorts any array. You don’t have to worry about what’s in the array. So we don’t have to worry about writing a sorting algorithm ourselves. We focus on the simpler task of comparing two elements.

    Now, imagine if there were no higher-order functions. We cannot pass functions to the.sort() method. When we need to sort different types of arrays, we have to write a new sort function, or we end up creating the same thing over and over again using function Pointers or objects. Either way would be unwieldy.

    Is because of the higher-order functions, we can be more functions and sorting, imagine if there was a clever browser engineers came up with a faster algorithm to realize the sort (), will benefit every programmer’s code, regardless of what they want to sort an array of internal elements, have a lot of high order array functions follow this pattern.

    This leads to the broader notion that the sort() method abstracts sorting tasks from arrays, and we have what’s called separation of concerns. Higher-order functions allow us to create abstractions that would otherwise be clunky or impossible to implement. Creating abstractions is 80% of software engineering.

    Every time we refactor code by removing duplicate parts, we are creating abstractions. This is like a pattern that you can replace with an abstract representation of that pattern. As a result, our code becomes cleaner and easier to understand. At least, that’s what I think.

    Higher-order functions are powerful tools for creating abstractions. There is also a related field of mathematics called category theory. More precisely, category theory is about discovering abstractions of abstractions. In other words, find patterns within patterns. For the past 70 years or so, smart programmers have been stealing their ideas in the form of programming language features and libraries.

    If we learn the patterns of these patterns, we can sometimes remove large chunks of code. Or break down complex problems into combinations of simple building speeds, and those building speeds are higher-order functions, and that’s why higher-order functions are important. Because of them, we have a powerful tool to combat complexity in our code.

    If you want to learn more about higher-order functions, here are some resources:

    Higher-Order Functions Chapter 5 of Eloquent JavaScript by Marijn Haverbeke.

    Higher-Order Functions Part of the Composing Sofware series by Eric Elliott.

    Higher-Order Functions in JavaScript by M. David Green for Sitepoint.

    Maybe you’re already using higher-order functions. JavaScript makes it so easy that we don’t think much about them. But when people throw around the word, we know what it is, it’s not complicated. But behind this deceptively simple concept lies enormous power.

    Update 3 July 2019: If you are an experienced functional programming developer, you may have noticed that I use impure functions and some verbose function names. This is not because I don’t understand impure functions or functional programming in general. This is not how I would define function names in a production environment. This is an educational article, so I try to choose practical examples that beginners can understand as a compromise. If you are interested, check out my other two articles Functional Purity and General Functional Programming Principles

    The last

    1. There are more than three ways to write the function, but we’ll talk about that next time.
    2. This is not always true. All three are slightly different in practice. The difference is thatthisThe keyword and function call stack tracks changes in tags during the process
    3. Wikipedia: Wikipedia (2019). ‘First — class citizen,’ Wikipedia, the Free Encyclopedia, Viewed 19 June 2019, En.wikipedia.org/wiki/First-…
    4. If you want to learn more about closures, see: Master the JavaScript Interview: What is a Closure? by Eric Elliott
    5. Higher Order Function (2014), Viewed 19 June 2019, wiki.c2.com/?HigherOrde… .