DEV Community

Cover image for JavaScript Closures: A Simple Explanation.
Kedar
Kedar

Posted on • Updated on

JavaScript Closures: A Simple Explanation.

Without any fancy intro, let's jump directly to what closure is.

Simply put, Closure is an inner function that remembers the environment it was created in.
Think of it like an aware function has access to the values (and parameters) from an outer function.

What makes a closure powerful is that it is capable of reading and manipulating the data of its outer functions.

Here's a very simple example of a closure.
Think of it as a code from an app. Our goal is to prompt the user to rate the app on every 3rd visit.

function promptRating() {
  var appUsage = 0;

  return function() {
    appUsagae++;
    if (appUsage % 3 === 0) console.log('Please rate the app.');
  }
};
Enter fullscreen mode Exit fullscreen mode

promptRating is a function that returns an inner function. This inner function is a closure. It remembers and has access to the variable appUsage from the outer function.

To see it in action:

// Init the outer function
var prompt = promptRating();

// Call `prompt` in regular workflow.
// If this is a multiple-of-3 visit, the user will be prompted to rate the app.
prompt(); // No Output
prompt(); // No Output
prompt(); // Output: Please rate the app.
Enter fullscreen mode Exit fullscreen mode

Being so simple yet powerful has its trade-offs: most notably when creating closures inside loops. Remember that closures have access to the data of the outer function.
So in a loop based on i, the code inside the closure will execute based on the current value of i. Not the old value of i which existed when the closure was created.
Here's a simple code to explain this:

function arrayOfNums(num) {
  var output = [];
  for (var i = 0; i < num; i++) {
    // Closure being pushed into the output array:
    output.push(function() { return i; }); 
  }
  return output;
}

var arr = arrayOfNums(3);
arr[0](); // Output: 3
arr[1](); // Output: 3
arr[2](); // Output: 3

Enter fullscreen mode Exit fullscreen mode

Another gotcha instance would be creating closures inside a timeout/interval. When run, the code inside the closure will execute based on the current data of the outer function. The values of this data may have gone stale before the timeout was met.

Here's a simple code to explain this:

function countdown(upto) {
  for (var i = 0; i < upto; i++) {
    // Closure set to run after 1000ms
    setTimeout(() => console.log(i), 1000);
  }
};

countdown(5); // Output: 5 5 5 5 5
Enter fullscreen mode Exit fullscreen mode

In conclusion, closures are simple beings. It is always an inner function which has access to the outer function scope.
If the outer function is called multiple times, every call creates a new closure. Closure's existence is dependent on their parent function's existence. Like I said, simple beings.

Top comments (2)

Collapse
 
stereobooster profile image
stereobooster

You can mention binding as well. let has lexical scope binding (e.g. it binded inside for ), where is var has scope of function body:

function countdown(upto) {
  for (let i = 0; i < upto; i++) {
    setTimeout(() => console.log(i), 1000);
  }
};
countdown(5); // Output: 0 1 2 3 4

Or using bind:

function arrayOfNums(num) {
  var output = [];
  for (var i = 0; i < num; i++) {
    output.push((function(x) { return x; }).bind(null, i)); 
  }
  return output;
}
arr[0](); // Output: 0
Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

Top tip, using markdown codeblocks where ''' is backtick

''' js
// Will give you syntax highlighting
'''