DEV Community

Cover image for Closures in JavaScript
Tanmay Agrawal
Tanmay Agrawal

Posted on

Closures in JavaScript

closures are a fundamental and powerful concept in JavaScript and many other programming languages. They allow functions to "remember" and access variables from their containing (enclosing) scope even after that scope has exited. To understand closures in detail, let's break down the key components and concepts associated with them.

  1. Nested Functions: Closures arise from the use of nested functions, which means defining one function within another. The inner function is said to be a closure because it "closes over" the variables from the outer function's scope. [[Nested Functions]]

  2. Lexical Scoping: JavaScript uses lexical Scoping, which means that the scope of a variable is determined by its location within the source code (the lexicon). In other words, it depends on where a variable is declared, not where it's called or used.

  3. Accessing Variables: Closures can access variables from their containing (outer) function, even after the outer function has completed execution and its scope has technically exited.

  4. Function as First-Class Citizens: In JavaScript, functions are first-class citizens, which means they can be assigned to variables, passed as arguments to other functions, and returned from functions. This flexibility is crucial for working with closures.

function outer() {
  let outerVar = 10;

  function inner() {
    console.log(outerVar);
  }

  return inner;
}

const closureFunc = outer();
closureFunc(); // This still logs "10" even though outer() has completed.

Enter fullscreen mode Exit fullscreen mode

In this example, inner is a closure because it "closes over" the outerVar variable from the outer function. Even after outer has completed execution, when we call closureFunc(), it can still access and log the value of outerVar.

Use Cases for Closures: Closures have several practical applications, such as:

  • Data encapsulation and information hiding: You can use closures to create private variables and methods.
  • Function factories: You can create functions that generate other functions with specific behavior.
  • Callbacks and event handling: Closures are commonly used to manage callbacks and event handlers.
  • Memoization: Closures can be used to cache results of expensive function calls.
  • Module patterns: Closures enable the creation of modules with private and public parts.

Closure Related Issues and Solution through IIFE

consider this code :

for (var i = 1; i <= 4; i++) {
    setTimeout(function () {
        console.log(i);
    }, i * 10000);
}

//console output
//5(4 times)
Enter fullscreen mode Exit fullscreen mode

In this code, we have a loop that runs from i values 0 to 4. Inside the loop, there's a setTimeout function that logs the value of i after a 1-second delay. You might expect this code to log 0, 1, 2, 3, and 4, each after a 1-second delay. However, it doesn't work as expected. It logs the value 5 five times after 1 second because the loop variable i is shared among all the timeouts.

Here's why this happens:

  1. The loop runs and increments i from 0 to 4.
  2. The setTimeout callbacks are scheduled to run after a 1-second delay, but they don't execute immediately.
  3. By the time the callbacks run, the loop has already finished, and i has a final value of 5.

So, all the setTimeout callbacks capture the same i, which is 5 when they execute. This behavior is because closures in JavaScript capture variables by reference, not by value. Therefore, all the callbacks see the same i, which is 5.

To solve this issue, developers often use the approach shown in your initial code snippet:

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

Enter fullscreen mode Exit fullscreen mode

In this version, an [[immediately invoked function expression (IIFE)]] is used to create a new scope for each iteration of the loop. The j variable inside the IIFE captures the current value of i for that specific iteration, preventing the closure over the loop variable problem. As a result, each setTimeout callback logs the correct value of j.

The closure related issues due to difference between using let and var

var: Variables declared with var in a loop often lead to unintended closure-related issues, as we saw in your initial code snippet. This is because they capture the variable by reference and not by value, causing all closures to share the same reference.

let: Variables declared with let in a loop create a new variable with each iteration, effectively capturing the value of the variable at that iteration. This behavior is why let is often preferred in situations where you want to avoid closure-related problems.

// Using var
for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}


Enter fullscreen mode Exit fullscreen mode

With var, there's a single i variable with function scope. All five setTimeout callbacks capture the same i by reference, so when they execute, they all see the final value of i, which is 5.

// Using let
for (let i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}

Enter fullscreen mode Exit fullscreen mode

With let, there are five separate i variables with block scope, one for each iteration. Each setTimeout callback captures its own i, and when they execute, they log the value of i from their respective block scopes, resulting in the expected output of 0, 1, 2, 3, and 4

let provides block-scoping, which is well-suited for situations like loop counters, where you want to avoid closure-related issues and ensure that each iteration has its own isolated variable. var, on the other hand, is function-scoped and can lead to unintended behavior in certain situations.
in order to better understand this read [[Difference between let and var]]

Emulating private methods with closures

JavaScript, prior to classes, didn't have a native way of declaring private methods, but it was possible to emulate private methods using closures. Private methods aren't just useful for restricting access to code. They also provide a powerful way of managing your global namespace.

const counter = (function () {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }

  return {
    increment() {
      changeBy(1);
    },

    decrement() {
      changeBy(-1);
    },

    value() {
      return privateCounter;
    },
  };
})();

console.log(counter.value()); // 0.

counter.increment();
counter.increment();
console.log(counter.value()); // 2.

counter.decrement();
console.log(counter.value()); // 1.

Enter fullscreen mode Exit fullscreen mode

each closure had its own lexical environment. Here though, there is a single lexical environment that is shared by the three functions: counter.incrementcounter.decrement, and counter.value.

The shared lexical environment is created in the body of an anonymous function, which is executed as soon as it has been defined(also known as [[immediately invoked function expression (IIFE)]])
The lexical environment contains two private items: a variable called privateCounter, and a function called changeBy. You can't access either of these private members from outside the anonymous function. Instead, you can access them using the three public functions that are returned from the anonymous wrapper.

Those three public functions form closures that share the same lexical environment. Thanks to JavaScript's lexical scoping, they each have access to the privateCounter variable and the changeBy function.


const makeCounter = function () {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment() {
      changeBy(1);
    },

    decrement() {
      changeBy(-1);
    },

    value() {
      return privateCounter;
    },
  };
};

const counter1 = makeCounter();
const counter2 = makeCounter();

console.log(counter1.value()); // 0.

counter1.increment();
counter1.increment();
console.log(counter1.value()); // 2.

counter1.decrement();
console.log(counter1.value()); // 1.
console.log(counter2.value()); // 0.

Enter fullscreen mode Exit fullscreen mode

Notice how the two counters maintain their independence from one another. Each closure references a different version of the privateCounter variable through its own closure. Each time one of the counters is called, its lexical environment changes by changing the value of this variable. Changes to the variable value in one closure don't affect the value in the other closure

Top comments (4)

Collapse
 
dsaga profile image
Dusan Petkovic

Thanks, really great examples!

I needed a lot of time to understand lexical scoping, and still don't quite get how exactly it works under the hood

Collapse
 
tanmaycode profile image
Tanmay Agrawal

It happens with my too, You can understand lexical scoping in several analogies, Maybe I will make one post to simplify the lexical scoping too

Collapse
 
dsaga profile image
Dusan Petkovic

Cool

Collapse
 
jonrandy profile image
Jon Randy 🎖️

Unfortunately, some of this post is incorrect. There are a number of widespread misconceptions about closures: