Understanding Closures in JavaScript: A Beginner’s Guide
A closure in JavaScript is a function that has access to its outer function’s variables and parameters, even after the outer function has returned. This means that closure can remember and access variables in the scope in which it was created, even if the function that created it has completed execution and its scope has been destroyed.
A closure is created when an inner function references a variable from an outer function. The inner function preserves a reference to the outer function’s variable, and that reference is called a closure.
Why Use Closures?
Closures are beneficial since they enable the development of private variables and functions in JavaScript. Such variables and functions can only be accessed within the scope of the function in which they were defined.
The closures feature will make your code deeply clear and modular. Suppose there is a need to keep a count within a function while ensuring that the count variable is not accessible from outside the function. In that case, a closure can be utilized to create a confidential counter variable.
Private Variables In Closures
Let’s say we have a function that needs to keep track of a counter, however, we do not want the counter to be accessible from outside the function. We can use a closure to create a private counter variable, like this:
function counter() {
let myCounter = 0
return function() {
myCounter++;
console.info(myCounter, 'myCounter')
}
}
const increaseCounter = counter()increaseCounter() // 1
increaseCounter() // 2
increaseCounter() // 3
Let’s have a look at the code a bit, the handleClick function generates a nested function that can access the count variable. To use this function as the callback for the addEventListener method, we assign it to the incrementClickCount variable. Each time the button is clicked, the incrementClickCount function is invoked and the count variable is increased. Because the count variable is defined inside the handleClick function, it cannot be accessed outside the function, thereby making it a private variable.
Event Listeners In Closures
Closures are also most commonly used with event listeners. Let’s say we have a button that we want to add a click event listener to, however, we want to keep track of how many times the button has been clicked. We can use a closure to create a private cliced
variable and attach an event listener to the button like this:
<button id="myButton">Click me</button>
<script>
const myButton = document.getElementById('myButton');
function handleClick() {
let clicked = 0;
return function() {
clicked++;
console.info(`Button clicked ${clicked} times`);
}
}
const increaseClickCount = handleClick();
myButton.addEventListener('click', increaseClickCount);
</script>
In this example, the handleClick
function returns an inner function that has access to the clicked
variable. We set the increaseClickCount
variable to the returned inner function so that we can pass it as the callback function to the addEventListener
method.
Whenever we click the button, increaseClickCount
the function will be executed then our variable clicked will be increased. Since the clicked
variable is declared within the handleClick
function, it is not accessible from outside the function, making it a private variable.
Implementing a Private Module
Closures can also be used to create private modules in JavaScript. A private module is a module that has private variables and functions that are not accessible from outside the module.
const calculator = (function() {
let result = 0;
function add(x) {
result += x;
} function subtract(x) {
result -= x;
} function multiply(x) {
result *= x;
} function divide(x) {
result /= x;
} function getResult() {
return result;
} return {
add: add,
subtract: subtract,
multiply: multiply,
divide: divide,
getResult: getResult
}
})();calculator.add(5);
calculator.multiply(2);
console.info(calculator.getResult()); // prints 10
In this code block, the calculator
variable is assigned to an immediately invoked function expression (IIFE) that returns an object containing functions that can be used to perform arithmetic operations. The result
variable is declared within the closure of the IIFE and is not accessible from outside the calculator
module. This allows us to create a private module with private variables and functions that can only be accessed through the functions that are returned from the module.
These examples show the flexibility and usefulness of closures in JavaScript development. By leveraging the power of closures, we can create more modular, maintainable, and efficient code.
Implementing Callback Functions
Closures are commonly used in JavaScript to implement callback functions, too. A callback function is a function that is passed as an argument to another function and is executed when a certain event occurs or when a certain condition is happen.
function waitThenDo(callback, time) {
setTimeout(function() {
callback();
}, time);
}
function sayHello() {
console.info('Hello!');
}
waitThenDo(sayHello, 1000); // waits 1 second before logs 'Hello!'
In this example, the waitThenDo()
function takes two arguments.
1-) a callback function
2-) a time in milliseconds.
It uses the setTimeout()
method to delay the execution of the callback function by the specified time. When the time has passed, the callback function is run. In this case, the sayHello()
function is executed as the callback function and is run after a delay of 1 second.
Creating a Memoized Function in Closures
A memoized function is a function that caches the results of previous function calls and returns the cached result when the same input is used again. This technique can be used to optimize functions that are computationally expensive or have repetitive calculations. And in my opinion, this most functional thing for the closures in Javascript developments. Also, I believe that this sub-title -memoized functions- needs to be explained in a separate blog post and this functionally improves the performance indeed. Whenever we can use it, we must use it, it should not be an optional way to go.
function memoize(func) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args)
if (cache[key]) {
console.info('Returning from cache...')
return cache[key]
}
console.info('Calculating result...')
const result = func.apply(this, args)
cache[key] = result
return result
}
}
function expensiveFunction(x, y) {
console.info('Calculating...')
return x * y
}
const memoizedFunction = memoize(expensiveFunction)
console.log(memoizedFunction(5, 10)) // Calculating result...50
console.log(memoizedFunction(5, 10)) // Returning from cache...50
console.log(memoizedFunction(10, 20)) // Calculating result...200
console.log(memoizedFunction(10, 20)) // Returning from cache...200
Here the memoize()
function takes a function as input and returns a new function that caches the results of previous function calls. The cached results are stored in the cache
object, which is declared within the closure of the returned function. When the returned function is called with the same input, it returns the cached result instead of recalculating it. Therefore, we will not spend our resources since we are returning the result from the cache instead of calculating again.
Unlock the Power of JavaScript with this Comprehensive Guide to Closures for Beginners
With closures in JavaScript, you can take your coding skills to the next level and write more secure, efficient, and modular code.
By mastering the concepts and examples of closures in JavaScript, beginners can take their coding skills to the next level and create more efficient and powerful JavaScript applications.