DEV Community

Vikas V K
Vikas V K

Posted on • Updated on

Closures: A breakdown - Part 1

Closures are one of the most intriguing concepts in Javascript. I've been referring to a ton of resources for clarification. For me what worked was EJS and Namasthe Javascript. When I combined these two everything fell into place. Let's dive into dissecting closures. I'll be using the same example as in Namasthe JS with some refactoring for better understanding.

Here's the code that we are going to mess around with.

function outer() {

    for(var i=1; i<=5; i++) {
            setTimeout(() => {
                console.log(i)
            }, i*1000)
    }
    console.log("I run first");
}

outer();
Enter fullscreen mode Exit fullscreen mode

Take a moment to think about the output, get your neurons to start firing and then run the code in your fav IDE. Spoiler Alert! The output is going to be 'I run first' and then '6' logged 5 times in the console at the end of 5 seconds. '6' is logged each second as soon as you run the code. But, why '6'? why isn't it logging '1', '2', '3', '4', '5' after each second? The culprit is the var keyword in for loop.
The concepts at play here are scope of var, function call, async programming, closures and references. We'll go through it one by one. Firstly, it is the async behaviour of setTimeout, which is why 'I run first' gets logged in before '6'.

For a moment imagine, there's no outer() function, there's only a for loop. Here i will be added to window object and its value will change as the loop runs. setTimeout is run 5 times asynchronously and the callback function exist in parallel but with different timers. i*1000 gets resolved when for loop runs, but console.log(i) is still dormant. So, when the timer runs out the value of i has been changed and all these 5 copies of callback functions points to the same i which exists in the window object. And that i is '6' now.

Now let the outer() function exist and we call it. In the current situation 'var' scope is limited to outer(), since its function scoped. We run the program, outer() is called, for loop is run, same thing happens as above but i is not added to window object rather its in the local scope of outer(). So now when the timer runs out for the first setTimeout of 1 second, callback function wakes up from its sleep and finds i in closure formed with outer() function.

image1

Closure is basically a function and its environment that's created each time a function is called. This environment has reference to the instance of bindings of its parent and above when the function was created. This feature helps functions to get access to bindings in its outer scope. To put it in different terms, a child function knows the assets(bindings/variables) owned by its parent, grandparent and so on. Whenever a child is called, it recalls its roots if need be.

Now i is up for grabs with the closure formed with outer(). Remember outer() is called just once and therefore there's only one instance of this environment. Even in this environment i is changed by for loop. So all the copies of callback function points to i which is already '6'. All because of the powerful scope of var.

Our goal is to get the output as '1', '2', '3', '4', '5'. For that we need a new instance of i for every step of the for loop. In 2015, a new keyword let was introduced in JS which is block scoped. Since the block is run every time the loop condition is met, a new instance of i is created for every block with references to different values. Here one should note that, there are 5 blocks existing simultaneously, with different values of i. So when the timer runs out, the callback remembers its block environment which has a particular value of i, kind of a closure but there is no parent function involved. Hence we'll get the desired output.

Alt Text

What if we don't want to use let but want to continue with var. Here the idea is to hijack value of i and call a function each time the loop is run, so that we have a new closure environment with each value of i.

function outer() {

    for(var i=1; i<=5; i++) {
        function inner(a) {
            setTimeout(() => {
                console.log(a)
            }, a*1000)
        }
        inner(i);
    }
    console.log("I run first");
}

outer();
Enter fullscreen mode Exit fullscreen mode

As per the above code, the value of i is taken over by a each time the loop runs. Since the function inner() is called 5 times, there are 5 closures with inner() existing in parallel which have different values of i captured by a.

Alt Text

Now 5 timers have started ticking at almost the same time. First, the timer for 1 second ends, callback function wakes up from its dormant state to find a. But where is a? a is in the closure formed with inner() function where a was set to '1'. To put it in GOT words, the callback function remembers. It knows a was 1, when the timer was set to 1 second . Remember there's 5 different copies of the callback function and 5 closure instances of a with values '1', '2', '3', '4', '5'. Each copy gets called when its timer runs out.

I would suggest you to use the debugger in your browser to thoroughly understand the workings of this. I have attached some screenshots for reference for each method i.e var, let and var with a inner function. In the end it's all magic by JS.

Comments/Corrections are welcome.

Top comments (0)