JavaScript Closures simply explained
Adam Blazek
Posted on July 10, 2023
Closures are one of the main pillars of JavaScript. It is also a popular interview question. So, it's really useful to understand what closures are and what we can do with them.
Understanding Closures
Simply said, closures allow accessing variables outside the function. Closure is a combination of a function bundled together with references to its surrounding state. It means that the function can access and manipulate variables located in the outer function's scope even after the outer function has finished. Then if the function is called at a time when the external scope with the variable already doesn't exist, the function still keeps access to the variable. It creates a bubble around our function, it keeps all variables in a scope when the function was created. Let's take a look at the following code snippet. It is a classic example.
const outerFunction = (outerVariable) => {
const innerFunction = (innerVariable) => {
console.log('outerVariable:', outerVariable);
console.log('innerVariable:', innerVariable);
}
return innerFunction;
}
const newFunction = outerFunction('outside');
newFunction('inside');
// logs outerVariable: outside
// logs innerVariable: inside
innerFunction
is a closure that is created when we call outerFunction
and it keeps access to the outerFunction
scope after the outerFunction
was executed.
Closure is like a protective bubble. Closure for the innerFunction
keeps the variables in the function's scope alive as long as the function exists.
Practical examples
Closures are used a lot in JavaScript, even when we don't always notice it. For example in data privacy, factory functions, stateful functions, and can often be found in JavaScript libraries and frameworks.
Now, we will go through two practical examples where we will explain how the Closures work.
Private variables
JavaScript doesn't support private variables but we can achieve a similar behavior by using a Closure. When we don't want to allow direct access to a variable, we can create accessors - the getters and setters - to enable them outside of the function. Let's describe it better in the following example:
const post = () => {
let likes = 0; // private variable
return {
getLikes: () => {
return likes;
},
like: () => {
return likes++;
};
}
}
var post1 = post();
post1.like();
console.log(post1.likes); // undefined
console.log(post1.getLikes()); // 1
var post2 = post();
console.log(post2.getLikes()); // 0
First, we declare the private variable likes
inside the function's constructor. likes
is in the function's scope and isn't accessible from outside. Then we create the accessor method for likes count. Defining the getter we give read-only access to the value. The second method is for incremental increasing the number of likes. No one can change the value more than we allow. In the end, if you check the console, you'll see that we can't get likes
directly. But, we can find out the value by using getLikes()
, or add more likes by calling like()
.
Callback as a Closure
It is another practical example of when we do asynchronous fetching of data and then updating the UI or logging. Check out this example.
const fetchData = (url, callback) => {
// It is simulating an asynchronous operation
setTimeout(() => {
const data = { name: 'Adam', age: 20 };
callback(data);
}, 2000);
}
const processUserData = (user) => {
console.log('Name:', user.name);
console.log('Age:', user.age);
}
fetchData('https://example.com/api/user', processUserData);
fetchData
function takes two parameters. The first one is an url from which we fetch data. The second one is the callback that will be called after the asynchronous call is done. processUserData
function is our callback function and also it is the Closure! When the fetchData
is executed then setTimeout
is called and the callback has access to the outer user data. And the access remains even after the execution of the fetchData
is finished.
This is a common way how callback can access data returned by async fetch and then do various things, for example, update the UI.
Disadvantage
As you can see, the biggest downside is that Closure comes with an extra bag of other variables. This can use up more memory than needed if we're not careful.
Summary
We talked about a Closure as a function having access to variables that were in the outer scope at the time when the closure was defined. Closures have many practical applications. We describe how are used as accessors to private variables - the getters and setters - or together with a callback function for updating the user interface based on asynchronously fetched data.
Posted on July 10, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.