Simplifying Closures in JavaScript

ยท

5 min read

Simplifying Closures in JavaScript

What exactly is a Closure?

It is the combo of a function bundled together with references to its surrounding state (the lexical environment). Basically, it gives you access to an outer function's scope from an inner function. In a closure, an inner function has access to the outer (enclosing) functionโ€™s variables โ€” a scope chain.

So, what is the scope chain of a closure?

It has:

๐Ÿ‘‰ access to its own scope. This refers to the variables defined between its curly brackets.

๐Ÿ‘‰ access to the outer functionโ€™s variables.

๐Ÿ‘‰ access to the global variables.

You might remember a word I used, "Lexical Scoping". Let's first understand what it is!


Lexical Scoping

It defines the scope of a variable according to the position of that variable declared in the source code.

Example:

let fname = 'Ayesha';

function full_name() { 
    let lname= 'Sahar';
    console.log(fname + ' '+ lname);
}

Explanation:

Here, the variable fname is a global variable, so it is accessible from anywhere. But the variable lname is a local variable, so it is accessible only within the full_name() function. If we try to access the msg variable outside the full_name() function, we will get an error.

So, JavaScript uses the "scope" to manage the variable accessibility.

According to lexical scoping, the scopes can be nested and the inner function can access the variables declared in its outer scope.

Example:

function greeting() {
    let msg = 'Hello World :)';

    function sayHello() {
        console.log(msg);
    }
    sayHello();
}

greeting();

Explanation:

๐Ÿ‘‰ The greeting() function creates a local variable named msg and another function named sayHello().

๐Ÿ‘‰ The sayHello() is the inner function and it is available only within the body of the greeting() function.

๐Ÿ‘‰ The sayHello() function can access the variables of the outer function such as the msg variable of the greeting() function.

๐Ÿ‘‰ Inside the greeting() function, we call the sayHello() function to display the message "Hello World :)'.


JavaScript Closures

To make a closure, let's just modify our greeting() function, which was created previously.

Example:

function greeting() {
    let msg = 'Hello World :)';

    function sayHello() {
        console.log(msg);
    }

    return sayHello;
}
let hello = greeting();
hello();

Explanation:

๐Ÿ‘‰ Here, instead of executing the sayHello() function inside the greeting() function, the greeting() function returns the sayHello() function object.

๐Ÿ‘‰ Since functions are very important in JavaScript, you can return a function from another function. They practically get a celebrity treatment๐Ÿ‘€

๐Ÿ‘‰ Outside of the greeting() function, we assigned the hello variable the value returned by the greeting() function. That is actually a reference to the sayHello() function!

๐Ÿ‘‰ We then executed the sayHello() function using the reference of that function: hello().

๐Ÿ‘‰ What's quite intriguing is that normally, a local variable only exists during the execution of the function. In the ideal case, when the greeting() function has been executed, the msg variable should be no longer accessible.

๐Ÿ‘‰ But here, we execute the hello() function that references the sayHello() function, so the msg variable still exists and can be accessible.

The sayHello() function is the magical being, also known as a closure!

So, we can conclude that the closure is a function that preserves the outer scope in its inner scope.


Closures in a loop

Let's start with an example as usual ;)

Example:

for (var j = 1; j <= 3; j++) {
    setTimeout(function () {
        console.log('After ' + j + ' second(s)=' + j);
    }, j * 1000);
}

//Output
//After 4 second(s)=4
//After 4 second(s)=4
//After 4 second(s)=4

Explanation:

๐Ÿ‘‰ We get the same message thrice.

๐Ÿ‘‰ What we actually wanted to do in the loop is to copy the value of j in each iteration at the time of iteration to display a message after 1s, 2s and 3s.

๐Ÿ‘‰ So, why did we see the same message after 4 seconds? It's because the callback passed to the setTimeout() is a closure. It remembers the value of j from the last iteration of the loop (value was 4).

๐Ÿ‘‰ Also, all closures created by the for-loop share the same global scope access and the same value of j.

๐Ÿ‘‰ This is quite an issue and to fix this, we need to create a new closure scope in each iteration of the loop.

There are two most used solutions:

  • let keyword
  • IIFE

let keyword

We can use the let keyword to declare a variable that is block-scoped in ES6. Once we use the let keyword in the for-loop, it will create a new lexical scope in each iteration. So, we will have a new j variable in each iteration. Also, our latest lexical scope is actually chained up to the previous scope. This means that the previous value of j is copied from the previous scope to the new one.

for (let j = 1; j <= 3; j++) {
    setTimeout(function () {
        console.log('After ' + j + ' second(s)=' + j);
    }, j * 1000);
}

//Output
//After 1 second(s)=1
//After 2 second(s)=2
//After 3 second(s)=3

IIFE

We can use an "immediately invoked function expression(IIFE)". This is because an IIFE creates a new scope by declaring a function and immediately executing it.

Here, we don't need to abandon var keyword :)

for (var j= 1; j <= 3; j++) {
    (function (j) {
        setTimeout(function () {
            console.log('After ' + j + ' second(s)=' + j);
        }, j * 1000);
    })(j);
}

//Output
//After 1 second(s)=1
//After 2 second(s)=2
//After 3 second(s)=3

Why learn Closures?

I'm listing down some uses of closures here:

๐Ÿ‘‰ Init functions - used to ensure that a function is only called once

๐Ÿ‘‰ Memory optimization -used to make functions more memory efficient and performant

๐Ÿ‘‰ Encapsulation - used to hide variables of a function and selectively expose methods

๐Ÿ‘‰ Functional programming - without them, higher-order functions and currying is not possible


Let's connect!

โœจ Twitter

โœจ Github

ย