Skip to main content

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

JavaScript features

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
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
As you can see, we’re unable to access the variable iamlocaloutside 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
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
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
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 and b, and finally the closure has access only to a.

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 closureouterScope,todaysGreeting, and myCounterexamples.
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
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
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;
    }
  }
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
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
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
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));
      }
    }
  };
}
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))

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.

Comments

Popular posts from this blog

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...