DEV Community

loading...
Cover image for Understanding JavaScript Closures in Loops

Understanding JavaScript Closures in Loops

imkarthikeyan profile image Karthikeyan Originally published at imkarthikeyans.hashnode.dev ・4 min read

In this blog, We will be going over the usage of the closures concept in javascript when working with loops.

I have written a blog explaining, how closure works under the hood. Please give it a read before jumping in.

Getting Started with Javascript Closure - Article Link

If you are already familiar with the concept of Closure, then let's dive in.

giphy

A quick definition of Closure

Closure is when a function remembers the scope where it has been created even when the function is executed in a different scope.


const trap = 'It is a trap'

function foo(){
 const inside = 'I have access'
  return function bar(){
    console.log(trap +'but '+ inside)
   }
}

/* `inside` will be garbage collected the moment `foo()` is executed */

const goo = foo() 

/*  Magic happens because of closure as it will have access to the value of the variable inside. */

goo() // It is a trap but I have access

Enter fullscreen mode Exit fullscreen mode

There was a misconception when using closure inside the loop. Let's take a look at the example,

var LIMIT = 3;

function multiplyBy3() {
  for (var i = 1; i <= LIMIT; i++) {
    setTimeout(function () {
      console.log(i * 3);
    }, 1000);
  }
}

multiplyBy3()
Enter fullscreen mode Exit fullscreen mode

You would have expected the output to

3
6
9
Enter fullscreen mode Exit fullscreen mode

But when you ran this on this console the output would have been

12
12
12
Enter fullscreen mode Exit fullscreen mode

Let's break it down shall we,

LIMIT will be created inside the global memory with a value of 3. When the pointer moves to the next line, the function definition will be referenced under multiplyBy3.

When the pointer hits the function call, multiplyBy3 will be pushed to the call stack. A new execution context will be created for the function with its own local memory.

Since setTimeout is not a part of the Javascript runtime, browser APIs will take care of spinning the timer for 1ms and push it to the callback queue. The anonymous callback function that is, the closure inside the setTimeout function will be waiting in the callback queue for its turn to enter the call stack for its execution.

So similarly, three closures will be waiting in the callback queue. Since they are created inside the function, they will have the same lexical environment shared by all of the closures.

One more crucial note here is since they share the same single lexical environment or in this case single variable i which will be changing for every iteration.

Since the variable is declared with the var keyword it will be hoisted to the top and it will be accessible within the function.

The code will look somewhat like this

var LIMIT = 3;

function multiplyBy3() {
    var i;
  for (i = 1; i <= LIMIT; i++) {
    setTimeout(function () {
      console.log(i * 3); // i will be 4
    }, 1000);
  }
console.log(i) // 4
}

multiplyBy3()
Enter fullscreen mode Exit fullscreen mode

The value of i will be determined when the anonymous function blocks are executed. Since the loop has already executed, the value of i will be pointing to 4. This will result in output 12 for three times.

Let's see the ways we can solve this problem

https://media.giphy.com/media/fxUJDxHZUQ5P7kRpKI/giphy.gif

Solution 1: Using let Keyword:

The let keyword introduced in ES2015 will do the trick. When we replace the var keyword with let, in the for loop, it will create the block scope, and the new copy of i will be created and attached to the anonymous function block every time.

let LIMIT = 3;

function multiplyBy3() {
  for (let i = 1; i <= LIMIT; i++) {
    setTimeout(function () {
      console.log(i * 3);
    }, 1000);
  }
}

multiplyBy3()
Enter fullscreen mode Exit fullscreen mode

If we try to access it outside of the for loop, we will end up with the undefined error.

let LIMIT = 3;

function multiplyBy3() {
  for (let i = 1; i <= LIMIT; i++) {
    setTimeout(function () {
      console.log(i)
      console.log(i * 3);
    }, 1000);
  }
console.log(i) // undefined
}

multiplyBy3()
Enter fullscreen mode Exit fullscreen mode

Solution 2: Wrapping it up with IIFE

The second solution would be wrapping up the IIFE (immediately invoked function expression ) pattern. That is, we put all the code inside our loop into a function, and then call that function immediately.

let LIMIT = 3;

function multiplyBy3() {
  for (var i = 1; i <= LIMIT; i++) {
    (function(){
            var j = i;
      setTimeout(function () {
         console.log(j * 3);
      }, 1000);
    })();
  }
}

multiplyBy3()
Enter fullscreen mode Exit fullscreen mode

Solution 3: Extract the inner body of the loop to function

The final solution will be to use the function factory. The function calculate(i) will create a new lexical environment for each callback in which i refers to the corresponding value of the loop.

var LIMIT = 3;

function multiplyBy3() {
  for (var i = 1; i <= LIMIT; i++) {
    calculate(i);
  }
}

function calculate(i) {
  setTimeout(function () {
    console.log(i * 3);
  }, 1000);
}

multiplyBy3()
Enter fullscreen mode Exit fullscreen mode

mp4.gif

That's it, folks. Thanks for sticking around. If I have missed something, Let me know in the comment section. I will add it in. Hit the 👍 or other emojis if you have liked the blog. More on the way.

Stay Safe and Happy Coding

Resources:

  1. Closures — MDN
  2. JS Visualiser
  3. Getting Started with Closure in Javascript

Discussion (0)

pic
Editor guide