Understanding closures in JavaScript

Understanding closures in JavaScript

ยท

9 min read

Introduction

In this article, we will learn about closures in JavaScript.

Every JavaScript developer should be aware of closures as it's the most frequently asked interview question as well as understanding it clearly will help you to avoid creating bugs in your code.

So let's get started.

What is closure?

Before diving into closures, we first need to understand how scope works in JavaScript.

Take a look at the below code:

function display() {
 var i = 10;
 console.log(i); // 10
}

display();
console.log(i); // Uncaught ReferenceError: i is not defined

When we declare a variable with var/let/const keyword inside a function, it's only available inside that function which means it's scoped to that function only because it becomes a private variable.

If you execute the above code, you will see that inside the function, console.log will print the value 10 as the variable i is available inside the function scope.

But outside the function the variable will not exist so when we try to print it, Uncaught ReferenceError is displayed.

Now, take a look at the below code:

function add(x) {
 var sum = 5;

 function result(y) {
  sum = sum + (x * y);
  return sum;
 }

 return result;
}

var output = add(4); // output will contain the result function
console.log(output(5)); // 25
console.log(output(10)); // 65

In the above code, we have defined result function inside the add function and from the add function we're returning the result function.

So in the first console.log we get 25 as the output because sum is initialized to 5 inside the function so 5 + (4 * 5) = 25.

When we again call the function stored in the output variable by passing 10, even though the sum function has finished executing, it will still retain the value of sum variable calculated last time which was 25 so the output will be 65 because 25 + (4 * 10) = 65.

So here comes the definition of a closure.

A function when defined inside another function and is returned from the outer function, will retain access to all the variables that were in the scope when the inner function was defined even after the outer function is terminated.

So in short in the above code, when we defined the result function, it has access to the sum variable and we're returning the result function from the add function so the result function will retain access to the sum variable even after the add function has finished executing.

This is known as closure.

Closures are very useful as it provides a way to create private variables which are only accessible from inside the function and will retain its value across the function calls.

So we cannot access the sum variable directly and it's only accessible through the call to the result function avoiding the misuse of the sum variable.

Take a look at the below code:

function getCharacter(name) { 
 let i = 0;
 return function next() {
  const value = name[i++];
  return value;
 }
}

const next = getCharacter('hello');

console.log(next()); // h
console.log(next()); // e
console.log(next()); // l
console.log(next()); // l
console.log(next()); // o

In the above code, variable i will retain its value across the multiple function calls so we're able to get each character in every function call using closures.

We can re-write the above code as below:

function getCharacter(name) { 
 let i = 0;

 return {
  next: function() {
   const value = name[i++];
   return value;
  }
 };
}

const obj = getCharacter('hello');

console.log(obj.next()); // h
console.log(obj.next()); // e
console.log(obj.next()); // l
console.log(obj.next()); // l
console.log(obj.next()); // o

In the above code, we're returning an object from the function and the object has function as a property so it will also be a closure.

Now, take a look at the below code:

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

This is a very famous interview question.

You might think that the above code will print numbers from 0 to 4 but it actually prints the number 5 five times.

This is because we have used setTimeout function inside the for loop so by the time, the setTimeout executes(which is after 1 seconds), the for loop is already finished executing.

So the value of i is 5 when the for loop is finished so the setTimeout function will print the value 5 five times.

There are two ways we can fix this issue.

  • using closures:
for(var i = 0; i < 5; i++) {
 (function(i) { 
   setTimeout(function() {
    console.log(i);
   }, 1000) 
  })(i);
}

The above code will correctly print the values from 0 to 4.

This is because by wrapping the setTimeout call in a function, we're creating a unique scope for each interaction.

So the value we're passing to the inner function at (i) will be passed to the function as a parameter function(i).

  • using let:

ES6 has added a let keyword which creates a new scope for each iteration when used inside a loop so we can just use the let keyword instead of var which will correctly print the value from 0 to 4.

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

Now, take a look at the below code:

var numbers = {};
for(var i = 0; i < 10; i++){
 numbers[i] = function() { 
   console.log(i);
 }
}

console.log(numbers[0]()); // 10
console.log(numbers[1]()); // 10
console.log(numbers[2]()); // 10

Here, also we get the value 10 printed every time instead of the actual value of the variable i.

We can fix this in 2 ways.

  • using let instead of var
  • defining the function outside the loop like this:
var numbers = {};

function log(value) {
 return function getValue() {
  return value;
 }
}

for(var i = 0; i < 10; i++){
 numbers[i] = log(i);
}

console.log(numbers[0]()); // 0
console.log(numbers[1]()); // 1
console.log(numbers[2]()); // 2

In the above code, we have defined the getValue function inside the log function so we created a closure that will retain the value of i which was passed to it.

So we get the correct expected output.

Conclusion

That's it about this article.

I hope you've been enjoying my articles and tutorials I've been writing. If you found them useful, consider buying me a coffee! I would really appreciate it.

Don't forget to subscribe to get my weekly newsletter with amazing tips, tricks, and articles directly in your inbox here.

Did you find this article valuable?

Support Yogesh Chavan by becoming a sponsor. Any amount is appreciated!