Closures in JavaScript
This article explains closures in JavaScript.
YouTube Video
Closures in JavaScript
In JavaScript, a 'closure' is one of the very important and powerful concepts. By understanding closures, you can gain knowledge that is useful in many situations, such as the behavior and scope of functions, as well as JavaScript's asynchronous processing and event handling. Here, we will explain in detail everything from the basic definition of closures to concrete examples and their applications.
What is a Closure?
A closure refers to the mechanism by which a function can access variables in its creation scope even when called outside that scope. By utilizing closures, it becomes possible for functions to continually 'remember' external variables.
Closures consist of the following two elements.
-
Function definition (the function itself)
-
Scope in which the function is defined (variables and other functions outside the function itself)
In JavaScript, closures are possible because functions have the capability to access variables within the scope they were created in.
Basic Example
First, let's look at a basic example of a closure. In the following code, outerFunction
returns a function called innerFunction
. The important point is that innerFunction
can access the variable count
defined within the scope of outerFunction
.
1function outerFunction() {
2 let count = 0;
3
4 function innerFunction() {
5 count++;
6 console.log(`Current count: ${count}`);
7 }
8
9 return innerFunction;
10}
11
12const counter = outerFunction();
13counter(); // Current count: 1
14counter(); // Current count: 2
15counter(); // Current count: 3
How Closures Work
As seen in the above example, count
is retained by innerFunction
even after outerFunction
has been executed. innerFunction
can continue to access the scope of outerFunction
, and thus count
is updated within innerFunction
. This is the basic mechanism of a closure.
It is innerFunction
that is assigned to the counter
variable, and we can see that the state of count
is preserved even though outerFunction
has already completed execution. This is because JavaScript continually 'remembers' the scope at the time of function definition.
Application: Closures as Private Variables
Closures can be used like 'private variables' in object-oriented programming. Normally, in JavaScript, object properties are directly accessible from outside, but by using closures, it is possible to prevent direct manipulation of variables within the function scope from outside.
In the next example, the createCounter
function uses a closure to create a counter and returns a counter with a private variable count
.
1function createCounter() {
2 let count = 0;
3
4 return {
5 increment: function() {
6 count++;
7 console.log(`Count: ${count}`);
8 },
9 decrement: function() {
10 count--;
11 console.log(`Count: ${count}`);
12 },
13 getCount: function() {
14 return count;
15 }
16 };
17}
18
19const myCounter = createCounter();
20myCounter.increment(); // Count: 1
21myCounter.increment(); // Count: 2
22myCounter.decrement(); // Count: 1
23console.log(myCounter.getCount()); // 1
In this example, count
is within the scope of the createCounter
function, so it cannot be accessed directly from outside. However, it can be manipulated through the increment
and decrement
methods. In this way, by using closures, you can incorporate the concept of private variables in JavaScript.
Practical Examples of Closures
Combination with Callback Functions
Closures are often used in combination with callback functions to manage asynchronous processing. For example, let's consider an example using a timer.
1function startTimer(duration) {
2 let timeLeft = duration;
3
4 function countdown() {
5 console.log(`Time left: ${timeLeft} seconds`);
6 timeLeft--;
7
8 if (timeLeft >= 0) {
9 setTimeout(countdown, 1000);
10 }
11 }
12
13 countdown();
14}
15
16startTimer(5);
17// Time left: 5 seconds
18// Time left: 4 seconds
19// Time left: 3 seconds
20// Time left: 2 seconds
21// Time left: 1 second
22// Time left: 0 seconds
In this example, the countdown
function accesses the timeLeft
variable within the scope of startTimer
. In this way, closures are very useful for asynchronous processing like timers, as they maintain the state of variables over time.
Event Handlers
Closures are also convenient when setting up event handlers. In the following example, a closure is used to record the number of times a button is clicked.
1function setupClickCounter(buttonId) {
2 let clickCount = 0;
3
4 const button = document.getElementById(buttonId);
5 button.addEventListener('click', function() {
6 clickCount++;
7 console.log(`Button clicked ${clickCount} times`);
8 });
9}
10
11setupClickCounter('myButton');
In this case, the clickCount
increases with each click, and its value is retained. By using closures, you can assign an independent counter to each button.
Conclusion
Closures are a concept that symbolizes the flexibility and power of JavaScript. They hold variables enclosed within a function scope and allow operations on those variables through externally accessible functions. By understanding and utilizing this mechanism, you can acquire more advanced techniques in JavaScript coding.
Closures are applied in various situations, from event handling and asynchronous processing to even pseudo-implementations of object-oriented programming.
You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.