DEV Community

Cover image for A closer look at JavaScript closures, higher-order functions, and currying
Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

A closer look at JavaScript closures, higher-order functions, and currying

Written by Gbolahan Olagunju✏️

Closures

Closures are one of the most powerful JavaScript features, but they can be a little daunting at first. Having a solid understanding of closures paves the way for understanding topics like higher-order functions and currying.

We’re going to address a few concepts that help illustrate the principles of closures, higher-order functions, and currying.

Functions in JavaScript are first-class citizens, which means that:

  1. Functions can be assigned to variables
  2. Functions can be passed as arguments to other functions
  3. Functions can return other functions
// functions can be assigned to variables
    const morningGreetings = (name) => {
      console.log(`Good morning ${name}`); 
    }
    const eveningGreeting = function (name) {
      console.log(`Good evening ${name}`);
    }


    // functions can be passed as arguments to other functions
    const todaysGreeting = (morningGreetings, eveningGreeting) => {
      morningGreetings('Barack')
      console.log(`Thanks for all you have done during the day`);
      eveningGreeting('Barack');
    }


    // functions can return other functions
     function myCounter ()  {
      let count = 0
      return function () {
         return ++count;
      }
    }
    const noOfTimes = myCounter();
    console.log(noOfTimes()); // 1
Enter fullscreen mode Exit fullscreen mode

The feature we’ll look closely at allows functions to return functions. The feature’s closure depends upon the unique characteristics of JavaScript.

In JavaScript, functions have the ability to reference a variable that isn’t defined in the function but is available within an enclosing function or the global scope.

Consider the following example:

const iamglobal = 'available throughout the programme';
    function funky() {
      const iamlocal = 'local to the function scope funky';
    }
    console.log(iamglobal);// available throughout the programme
    console.log(iamlocal); // iamlocal is not defined
Enter fullscreen mode Exit fullscreen mode

As you can see, we’re unable to access the variable iamlocal outside of the scope of the function funky. This is because the variable is only kept “alive” while the funky is active.

Once the function has been invoked, references to any variables declared within its scope are removed and the memory is handed back to the computer for use.

However, there’s a way we can have access to the variable declared within a function even after the function has been invoked.

This is where closures come in.

A closure is a reference to a variable declared in the scope of another function that is kept alive by returning a new function from the invocation of the existing function.

Let’s take a look at an example:

function outerScope() {
  const outside = 'i am outside';
  function innerScope() {
    const inside = 'i am inside';
    console.log('innerScope ➡️', outside);
    console.log('innerScope ➡️',inside);
  }
  console.log('outerScope ➡️', outside);
  innerScope();
}
outerScope();
// outerScope ➡️ i am outside
// innerScope ➡️ i am outside
// innerScope ➡️ i am inside
Enter fullscreen mode Exit fullscreen mode

It’s possible to access the value of the variable outside from function innerScope. The concept of closures hinges on this capability.

From the example above, it’s possible for us to return the function innerScope rather than call it within the outerScope, since this is close to a real world scenario.

Let’s modify the example above to reflect this change:

function outerScope() {
  const outside = 'i am outside';
  function innerScope() {
    const inside = 'i am inside';
    console.log('innerScope ➡', outside);
    console.log('innerScope ➡',inside);
  }
  return innerScope
}

const inner = outerScope();
inner();
// outerScope ➡️ i am outside
// innerScope ➡️ i am outside
Enter fullscreen mode Exit fullscreen mode

This resembles the example above, which illustrates how functions have the ability to return functions.

Let’s take this a step further and look at more of a real-world example:

function closure(a) { 
  return function trapB (b) {
    return function trapC(c) {
      return c * a + b; 
    }
  }
}

const oneEight = closure(1.8);
const thirtyTwo = oneEight(32);
const degreeToFahrenheit = thirtyTwo(30);
console.log(degreeToFahrenheit); // 86
Enter fullscreen mode Exit fullscreen mode

It can be useful to think of each function declaration as a circle in which each enclosing circle has access to the variables declared in the preceding circle:

In this case, trapC has access to variables a, b and c, while trapB has access to variable a and b, and finally the closure has access only to a.

LogRocket Free Trial Banner

Higher-order functions

Higher-order functions are functions that accept another function as an argument, return another function as a result, or both.

So far, we’ve been using higher-order functions as seen in our closure, outerScope,todaysGreeting, and myCounter examples.

Closures are integral to higher-order functions.

One of the core benefits of higher-order functions is that they allow us to customize the way we call our functions.

Consider the illustration below:

const multiply = (a , b) => {
  return a * b;
}
console.log(multiply(2,3)) // 6
Enter fullscreen mode Exit fullscreen mode

If we’re only interested in getting all the multiples of 2 throughout the entire program, you can repeat 2 as one of the arguments throughout our program:

multiply(2,1) // 2
multiply(2,2) // 4
multiply(2,3) // 6
Enter fullscreen mode Exit fullscreen mode

While this works, it introduces a lot of repetition into our code and it violates the DRY (Don’t repeat yourself) principle.

You could also argue that we can hardcode the value of 2 into our function definition. Well, that’s true, but it’ll make our function less reusable.

Let’s redefine the function to use higher-order functions so we can see the benefits and flexibility it offers when calling the function:

const multiply = (a) => {
    return (b) => {
      return a * b;
    }
  }
Enter fullscreen mode Exit fullscreen mode

Having defined the above function that way, we can create customize function calls as follows:

const multiplyByTwo = multiply(2);
console.log(multiplyByTwo(3)) // 6

const multiplyByThree = multiply(3);
console.log(multiplyByThree(6)); // 18
Enter fullscreen mode Exit fullscreen mode

We can create customize functions that have practical use and also save us the hassle of repeating ourselves.

Currying

Currying is a process that involves the partial application of functions.

A function is said to be curried when all the arguments needed for its invocation have not been supplied. In this case, it will return another function that retains the already-supplied arguments and expect the remaining omitted argument to be supplied before invoking the function.

The function is only invoked when all arguments have been supplied. Otherwise, a new function is returned that retains existing arguments and accepts new arguments as well.

When you curry a function, you call it as f(a)(b)(c)(d) rather than f(a, b, c , d) . By extension, all curried functions are higher-order functions but not all higher-order functions are curried.

The bottom line here is that currying allows us to turn a single function into a series of functions.

Let’s consider the following example:

function sum (a, b) {
  return a + b;
}
console.log(sum(4,5)) // 9
Enter fullscreen mode Exit fullscreen mode

We can go ahead and curry this function so we have the flexibility to call it partially when all arguments aren’t supplied.

function curriedSum (x,y)  {
    if (y === undefined) {
      return function(z) {
        return x + z
      }
    } else {
      return x + y;
    }
  }

 console.log(curriedSum(4, 5)) // 9
console.log(curriedSum(4)(5)) // 9
Enter fullscreen mode Exit fullscreen mode

We don’t have to write another curried implementation of our function every time we need it in order to call it partially. Instead, we can use a general curry function and pass our original function as an argument to it.

Here’s how:

function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

Let’s use an example to illustrate how this works.

function mean (a , b, c) {
return (a + b + c) / 3
}
const curriedMean = curry(mean);
console.log(curriedMean(1,2,3))
console.log(curriedMean(1,2)(3))
console.log(curriedMean(1)(2)(3))
Enter fullscreen mode Exit fullscreen mode

Conclusion

As you can see, these concepts build upon one another since closures are used extensively in higher-order functions, and higher-order functions are similar to curried functions.

Having a solid understanding of the above concepts gives us insight into how popular JavaScript libraries implement a few functions, e.g. the connect function used by React-Redux.

connect(mapState)(MyComponent)
Enter fullscreen mode Exit fullscreen mode

References

Currying

Currying is an advanced technique of working with functions. It’s used not only in JavaScript, but in other languages as well. Currying is a transformation of functions that translates a function from callable as f(a, b, c) into callable as f(a)(b)(c). Currying doesn’t call a function. It just transforms it.

JavaScript: Novice to Ninja, 2nd Edition

As comprehensive as it can get. In 600+ pages you’ll go from JavaScript Novice to Ninja. Covering everything from arrays, logic and loops, to functions, objects, DOM, events, testing and debugging, Ajax and more. It’s everything you need to build with JavaScript.

Implementing new JS features? Understand how JavaScript errors affect your users.

Tracking down the cause of a production JavaScript exception or error is time consuming and frustrating. If you’re interested in monitoring JavaScript errors and seeing how they affect users, try LogRocket.LogRocket Dashboard Free Trial Bannerhttps://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens on your site.LogRocket enables you to aggregate and report on errors to see how frequent they occur and how much of your user base they affect. You can easily replay specific user sessions where an error took place to see what a user did that led to the bug.

LogRocket instruments your app to record requests/responses with headers + bodies along with contextual information about the user to get a full picture of an issue. It also records the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

Enhance your JavaScript error monitoring capabilities – – Start monitoring for free.


The post A closer look at JavaScript closures, higher-order functions, and currying appeared first on LogRocket Blog.

Top comments (1)

Collapse
 
williamjpriest profile image
WilliamJPriest

Very insightful article, thanks for sharing