As you write code in JavaScript, you will likely come across many scenarios where closures prove useful.
In this article, we will discuss what closure functions are, how they work, and provide examples of their use.
What is closure in JavaScript?
A closure is a function that has access to variables in its outer (enclosing) function scope, even after the outer function has returned. In other words, A closure is a function having access to the parent scope. Closure means that an inner function always have access to the vars and parameters of it outer function even after the outer function has returned.
Closure’s Features
Closures can be created in a variety of ways, and can access variables in three scopes:: their own scope, the outer function’s scope, and the global scope. Lets look at examples of each scope in a closure function:
1) The closure’s own scope:
function createCounter() {
let count = 0; // variable in closure's own scope
return function() {
count++; // access to count variable in own sc
console.log(count);
}
}
The createCounter function returns a closure function that has access to its own scope, which includes the count variable. Each time the closure function is called, it increments the count variable and logs its value to the console.
2) The outer function’s scope:
function createGreeting(name) {
const greeting = "Hello, ${name}"; // variable in outer function's scope
return function() {
// access to greeting variable in outer function's scope
console.log(greeting);
}
}
const greetName = createGreeting('Tam');
greetName(); //Output: "Hello, Tam"
The createGreeting function returns a closure function that has no local variables of its own and has access to the greeting variable in the outer function’s scope. The greeting variable is defined in the outer function, and is then used in the returned closure function to log a greeting message to the console.
3)The global scope:
// variable in global scope
const globalVariable = 'I am in the global scope';
function createFunction() {
return function() {
console.log(globalVariable); // access to globalVariable in global scope
}
}
const myFunction = createFunction();
myFunction(); // Output: "I am in the global scope"
We define a globalVariable variable in the global scope, and then define function createFunction that returns a function with access to the global variable. We then create an instance of the function using myFunction = createFunction(), and call it using myFunction() to log the value of globalVariable to the console. The closure function is able to access globalVariable in the global scope, even though it is defined outside of the function.
When a closure is created, it maintains a reference to the variables that are in its outer scope, even after the outer function has returned.
function createCounter() {
let count = 0;
function increment() {
count++;
console.log(count);
}
return increment;
}
let counter1 = createCounter();
counter1(); // Output: 1
counter1(); // Output: 2
let counter2 = createCounter();
counter2(); // Output: 1
counter2(); // Output: 2
counter1(); // Output: 3
The createCounter function returns a closure that accesses the count variable in its outer scope. When we call createCounter twice to create two separate counters (counter1 and counter2), each closure maintains a reference to its own count variable. When we call counter1() and counter2() to increment their respective count variables, we can see that the count variable for each counter is incremented independently. However, when we call counter1() again, we can see that the count variable for counter1 has been modified by the closure, and the change is visible outside of the closure as well. This behavior is a result of closures maintaining references to their outer variables, which allows them to modify those variables even after the outer function has returned.
It’s important to keep in mind the rules and side effects of closures, such as the fact that they maintain references to their outer variables, and that they can lead to memory leaks if not used properly. Lets look at this example:
function createButton() {
let button = document.createElement('button');
let count = 0;
button.addEventListener('click', function onClick() {
count++;
console.log('Button clicked ' + count + ' times');
});
return button;
}
let container = document.getElementById('container');
for (let i = 0; i < 10000; i++) {
container.appendChild(createButton());
}
In the example above, function createButton returns a button element with a click event listener that increments a count
variable each time the button is clicked. We then use a loop to create 10,000 buttons and append them to a container element.
The problem with this code is that each button element has a closure associated with it, which maintains a reference to the count
variable even after the button has been clicked. This means that even if the button element is removed from the DOM, the closure and its associated count
variable remain in memory, which can cause memory leaks over time.
To avoid memory leaks in this case, we can remove the click event listener when the button is removed from the DOM, like this:
function createButton() {
let button = document.createElement('button');
let count = 0;
function onClick() {
count++;
console.log('Button clicked ' + count + ' times');
}
button.addEventListener('click', onClick);
function removeButton() {
button.removeEventListener('click', onClick);
container.removeChild(button);
}
return {
element: button,
remove: removeButton
};
}
let container = document.getElementById('container');
for (let i = 0; i < 10000; i++) {
let button = createButton();
container.appendChild(button.element);
setTimeout(button.remove, 5000);
}
we create a removeButton function that removes the click event listener from the button element and removes the button from the container element. We then modify the createButton function to return an object with two properties: element, which contains the button element, and remove, which is a reference to the removeButton function. Finally, we use a loop to create 10,000 buttons and append them to the container element. We also use a setTimeout function to remove each button from the DOM after 5 seconds, which ensures that the click event listener and closure are properly cleaned up and do not cause memory leaks.