We’ve already taken a close look at variable scope and hoisting, so today we’ll finish our exploration by examining three of the most important and heavily-used concepts in modern JavaScript development — closures, callbacks and IIFEs.
Key Takeaways
- Closures in JavaScript are functions that retain access to variables from their parent scope, even after the parent function has completed, allowing them to remember and manipulate these variables later.
- Callbacks are functions passed into other functions as arguments, which are then executed inside the outer function, providing a way to delay execution or maintain sequence in asynchronous operations.
- Immediately Invoked Function Expressions (IIFEs) are functions that are executed right after they are defined, used to protect the scope of variables and prevent pollution of the global scope.
- Closures can both read and update variables stored in their scope, and these updates are visible to any closures with access to these variables, demonstrating the concept of closures storing references to variables, not values.
- Using IIFEs helps in creating private scopes within a function, allowing for better management of variables and preventing external access to these variables.
- The combination of these concepts (closures, callbacks, and IIFEs) provides powerful tools for writing clean, efficient, and secure JavaScript code, encapsulating functionality and avoiding global scope pollution.
Closures
In JavaScript, a closure is any function that keeps reference to variables from its parent’s scope even after the parent has returned.
This means practically any function can be considered a closure, because, as we learned in the variable scope section from the first part of this tutorial, a function can refer to, or have access to –
- any variables and parameters in its own function scope
- any variables and parameters of outer (parent) functions
- any variables from the global scope.
So, chances are you’ve already used closures without even knowing it. But our aim is not just to use them – it is to understand them. If we don’t understand how they work, we can’t use them properly. For that reason, we are going to split the above closure definition into three easy-to-comprehend points.
Point 1: You can refer to variables defined outside of the current function.
function setLocation(city) {
var country = "France";
function printLocation() {
console.log("You are in " + city + ", " + country);
}
printLocation();
}
setLocation ("Paris"); // output: You are in Paris, France
In this code example, the printLocation()
function refers to the country
variable and the city
parameter of the enclosing (parent) setLocation()
function. And the result is that, when setLocation()
is called, printLocation()
successfully uses the variables and parameters of the former to output “You are in Paris, France”.
Point 2: Inner functions can refer to variables defined in outer functions even after the latter have returned.
function setLocation(city) {
var country = "France";
function printLocation() {
console.log("You are in " + city + ", " + country);
}
return printLocation;
}
var currentLocation = setLocation ("Paris");
currentLocation(); // output: You are in Paris, France
This is almost identical to the first example, except that this time printLocation()
is returned inside the outer setLocation()
function, instead of being immediately called. So, the value of currentLocation
is the inner printLocation()
function.
If we alert currentLocation
like this – alert(currentLocation);
– we’ll get the following output:
function printLocation () {
console.log("You are in " + city + ", " + country);
}
As we can see, printLocation()
is executed outside its lexical scope. It seems that setLocation()
is gone, but printLocation()
still has access to, and “remembers”, its variable (country
) and parameter (city
).
A closure (inner function) is able to remember its surrounding scope (outer functions) even when it’s executed outside its lexical scope. Therefore, you can call it at any time later in your program.
Point 3: Inner functions store their outer function’s variables by reference, not by value.
function cityLocation() {
var city = "Paris";
return {
get: function() { console.log(city); },
set: function(newCity) { city = newCity; }
};
}
var myLocation = cityLocation();
myLocation.get(); // output: Paris
myLocation.set('Sydney');
myLocation.get(); // output: Sydney
Here cityLocation()
returns an object containing two closures – get()
and set()
– and they both refer to the outer variable city
. get()
obtains the current value of city
, while set()
updates it. When myLocation.get()
is called for the second time, it outputs the updated (current) value of city
– “Sydney” – rather than the default “Paris”.
So, closures can both read and update their stored variables, and the updates are visible to any closures that have access to them. This means that closures store references to their outer variables, rather than copying their values. This is a very important point to remember, because not knowing it can lead to some hard-to-spot logic errors – as we’ll see in the “Immediately Invoked Function Expressions (IIFEs)” section.
One interesting feature of closures is that the variables in a closure are automatically hidden. Closures store data in their enclosed variables without providing direct access to them. The only way to alter those variables is by providing access to them indirectly. For example, in the last piece of code we saw that we can modify the variable city
only obliquely by using the get()
and set()
closures.
We can take advantage of this behavior to store private data in an object. Instead of storing the data as an object’s properties, we can store it as variables in the constructor, and then use closures as methods that refer to those variables.
As you can see, there is nothing mystical or esoteric around the closures – only three simple points to remember.
Callbacks
In JavaScript, functions are first-class objects. One of the consequences of this fact is that functions can be passed as arguments to other functions and can also be returned by other
functions.
A function that takes other functions as arguments or returns functions as its result is called a higher-order function, and the function that is passed as an argument is called a callback function. It’s named “callback” because at some point in time it is “called back” by the higher-order function.
Callbacks have many everyday usages. One of them is when we use the setTimeout()
and setInterval()
methods of the browser’s window
object – methods that accept and execute callbacks:
function showMessage(message){
setTimeout(function(){
alert(message);
}, 3000);
}
showMessage('Function called 3 seconds ago');
Another example is when we attach an event listener to an element on a page. By doing that we’re actually providing a pointer to a callback function that will be called when the event occurs.
// HTML
<button id='btn'>Click me</button>
// JavaScript
function showMessage(){
alert('Woohoo!');
}
var el = document.getElementById("btn");
el.addEventListener("click", showMessage);
The easiest way to understand how higher-order functions and callbacks work is to create your own. So, let’s create one now:
function fullName(firstName, lastName, callback){
console.log("My name is " + firstName + " " + lastName);
callback(lastName);
}
var greeting = function(ln){
console.log('Welcome Mr. ' + ln);
};
fullName("Jackie", "Chan", greeting);
Here we create a function fullName()
that takes three arguments – two for the first and last name, and one for the callback function. Then, after the console.log()
statement, we put a function call that will trigger the actual callback function – the greeting()
function defined below the fullName()
. And finally, we call fullName()
, where greeting()
is passed as a variable – without parentheses – because we don’t want it executed right away, but simply want to point to it for later use by fullName()
.
We are passing the function definition, not the function call. This prevents the callback from being executed immediately, which is not the idea behind the callbacks. Passed as function definitions, they can be executed at any time and at any point in the containing function. Also, because callbacks behave as if they are actually placed inside that function, they are in practice closures: they can access the containing function’s variables and parameters, and even the variables from the global scope.
The callback can be an existing function as shown in the preceding example, or it can be an anonymous function, which we create when we call the higher-order function, as shown in the following example:
function fullName(firstName, lastName, callback){
console.log("My name is " + firstName + " " + lastName);
callback(lastName);
}
fullName("Jackie", "Chan", function(ln){console.log('Welcome Mr. ' + ln);});
Callbacks are heavily used in JavaScript libraries to provide generalization and reusability. They allow the library methods to be easily customized and/or extended. Also, the code is easier to maintain, and much more concise and readable. Every time you need to transform your unnecessary repeated code pattern into more abstract/generic function, callbacks come to the rescue.
Let’s say we need two functions – one that prints information about published articles and another that prints information about sent messages. We create them, but we notice that some part of our logic is repeated in both of the functions. We know that having one and the same piece of code in different places is unnecessary and hard to maintain. So, what is the solution? Let’s illustrate it in the next example:
function publish(item, author, callback){ // Generic function with common data
console.log(item);
var date = new Date();
callback(author, date);
}
function messages(author, time){ // Callback function with specific data
var sendTime = time.toLocaleTimeString();
console.log("Sent from " + author + " at " + sendTime);
}
function articles(author, date){ // Callback function with specific data
var pubDate = date.toDateString();
console.log("Written by " + author);
console.log("Published " + pubDate);
}
publish("How are you?", "Monique", messages);
publish("10 Tips for JavaScript Developers", "Jane Doe", articles);
What we’ve done here is to put the repeated code pattern (console.log(item)
and var date = new Date()
) into a separate, generic function (publish()
), and leave only the specific data inside other functions – which are now callbacks. That way, with one and the same function we can print information for all sorts of related things – messages, articles, books, magazines and so on. The only thing you need to do is to create a specialized callback function for each type, and pass it as an argument to the publish()
function.
Immediately Invoked Function Expressions (IIFEs)
An immediately invoked function expression, or IIFE (pronounced “iffy”), is a function expression (named or anonymous) that is executed right away after its creation.
There are two slightly different syntax variations of this pattern:
// variant 1
(function () {
alert('Woohoo!');
})();
// variant 2
(function () {
alert('Woohoo!');
}());
To turn a regular function into an IIFE you need to perform two steps:
- You need to wrap the whole function in parentheses. As the name suggests, an IIFE must be a function expression, not a function definition. So, the purpose of the enclosing parentheses is to transform a function definition into an expression. This is because, in JavaScript, everything in parentheses is treated as an expression.
- You need to add a pair of parentheses at the very end (variant 1), or right after the closing curly brace (variant 2), which causes the function to be executed immediately.
There are also three more things to bear in mind:
First, if you assign the function to a variable, you don’t need to enclose the whole function in parentheses, because it is already an expression:
var sayWoohoo = function () {
alert('Woohoo!');
}();
Second, a semicolon is required at the end of an IIFE, as otherwise your code may not work properly.
And third, you can pass arguments to an IIFE (it’s a function, after all), as the following example demonstrates:
(function (name, profession) {
console.log("My name is " + name + ". I'm an " + profession + ".");
})("Jackie Chan", "actor"); // output: My name is Jackie Chan. I'm an actor.
It’s a common pattern to pass the global object as an argument to the IIFE so that it’s accessible inside of the function without having to use the window
object, which makes the code independent of the browser environment. The following code creates a variable global
that will refer to the global object no matter what platform you are working on:
(function (global) {
// access the global object via 'global'
})(this);
</code></pre>
<p>This code will work both in the browser (where the global object is <code>window</code>), or in a Node.js environment (where we refer to the global object with the special variable <code>global</code>). </p>
<p>One of the great benefits of an IIFE is that, when using it, you don’t have to worry about polluting the global space with temporary variables. All the variables you define inside an IIFE will be local. Let’s check this out:</p>
[code language="javascript"](function(){
var today = new Date();
var currentTime = today.toLocaleTimeString();
console.log(currentTime); // output: the current local time (e.g. 7:08:52 PM)
})();
console.log(currentTime); // output: undefined
In this example, the first console.log()
statement works fine, but the second fails, because the variables today
and currentTime
are made local thanks to the IIFE.
We know already that closures keep references to outer variables, and thus, they return the most recent/updated values. So, what do you think is going to be the output of the following example?
function printFruits(fruits){
for (var i = 0; i < fruits.length; i++) {
setTimeout( function(){
console.log( fruits[i] );
}, i * 1000 );
}
}
printFruits(["Lemon", "Orange", "Mango", "Banana"]);
You may have expected that the names of the fruits would be printed one after another at one-second intervals. But, in practice, the output is four times “undefined”. So, where is the catch?
The catch is that the value of i
, inside the console.log()
statement, is equal to 4 for each iteration of the loop. And, since we have nothing at index 4 in our fruits array, the output is “undefined”. (Remember that, in JavaScript, an array’s index starts at 0.) The loop terminates when i < fruits.length
returns false
. So, at the end of the loop the value of i
is 4. That most recent version of the variable is used in all the functions produced by the loop. All this happens because closures are linked to the variables themselves, not to their values.
To fix the problem, we need to provide a new scope – for each function created by the loop – that will capture the current state of the i
variable. We do that by closing the setTimeout()
method in an IIFE, and defining a private variable to hold the current copy of i
.
function printFruits(fruits){
for (var i = 0; i < fruits.length; i++) {
(function(){
var current = i; // define new variable that will hold the current value of "i"
setTimeout( function(){
console.log( fruits[current] ); // this time the value of "current" will be different for each iteration
}, current * 1000 );
})();
}
}
printFruits(["Lemon", "Orange", "Mango", "Banana"]);
We can also use the following variant, which does the same job:
function printFruits(fruits){
for (var i = 0; i < fruits.length; i++) {
(function(current){
setTimeout( function(){
console.log( fruits[current] );
}, current * 1000 );
})( i );
}
}
printFruits(["Lemon", "Orange", "Mango", "Banana"]);
An IIFE is often used to create scope to encapsulate modules. Within the module there is a private scope that is self-contained and safe from unwanted or accidental modification. This technique, called the module pattern, is a powerful example of using closures to manage scope, and it’s heavily used in many of the modern JavaScript libraries (jQuery and Underscore, for example).
Conclusion
The aim of this tutorial has been to present these fundamental concepts as clearly and concisely as possible – as a set of simple principles or rules. Understanding them well is key to being a successful and productive JavaScript developer.
For a more detailed and in-depth explanation of the topics presented here, I recommend you take a look at Kyle Simpson’s You Don’t Know JS: Scope & Closures.
Frequently Asked Questions (FAQs) about JavaScript Closures, Callbacks, and IIFEs
What is the difference between a closure and a callback in JavaScript?
In JavaScript, a closure is a function that has access to its own scope, the outer function’s scope, and the global scope. It’s a way of preserving a copy of the variables from its parent scope, even after the parent function has finished execution. On the other hand, a callback is a function passed into another function as an argument to be executed later. Callbacks are often used in asynchronous programming to handle the result of an operation, once it’s completed.
How does variable scope work in JavaScript callbacks?
In JavaScript, the scope of a variable defines its accessibility or visibility in different parts of your code during runtime. When a variable is defined inside a function, it’s only accessible within that function – this is known as local scope. However, when a variable is defined outside a function, it’s accessible globally, throughout your code – this is known as global scope. In the context of callbacks, if a variable is defined in the same scope as the callback function, it can be accessed within the callback.
What is an Immediately Invoked Function Expression (IIFE) in JavaScript and why is it used?
An Immediately Invoked Function Expression (IIFE) is a JavaScript function that runs as soon as it is defined. The syntax of an IIFE involves wrapping an anonymous function in parentheses to create a function expression, and then immediately invoking it with another pair of parentheses. IIFEs are used to avoid polluting the global scope by encapsulating variables within the function’s scope. They are also used to create fresh environments, where the same variable name can be used without conflicts.
How do closures help in data privacy and encapsulation in JavaScript?
Closures in JavaScript provide a way to achieve data privacy and encapsulation. They allow you to create private variables that can’t be accessed directly from outside the function. This is because a closure retains access to its parent scope, including any variables defined in that scope, even after the parent function has returned. This means you can create functions with private state – the variables defined in the parent scope.
How can I handle errors in JavaScript callbacks?
Error handling in JavaScript callbacks can be achieved using the “error-first” callback pattern. In this pattern, the first argument of the callback function is reserved for an error object. If an error occurred during the execution of the asynchronous operation, this argument will contain an Error object with details about the error. If no error occurred, this argument will be null. This pattern allows you to handle errors in a consistent manner across your asynchronous JavaScript code.
Can you provide an example of a closure in JavaScript?
Sure, here’s a simple example of a closure in JavaScript:function outerFunction() {
let count = 0;
function innerFunction() {
count++;
console.log(count);
}
return innerFunction;
}
const counter = outerFunction();
counter(); // Outputs: 1
counter(); // Outputs: 2
In this example, innerFunction
is a closure that is returned by outerFunction
. It retains access to the count
variable from outerFunction
‘s scope, even after outerFunction
has finished execution.
What is callback hell and how can it be avoided in JavaScript?
Callback hell, also known as pyramid of doom, refers to the scenario where callbacks are nested within callbacks, making the code hard to read and understand. It’s a common problem in JavaScript when dealing with asynchronous operations. It can be avoided by using techniques like modularization (breaking callbacks into independent functions), using Promises, or async/await syntax which can make your asynchronous code look and behave like synchronous code.
Can you provide an example of an IIFE in JavaScript?
Sure, here’s a simple example of an IIFE in JavaScript:(function() {
let message = 'Hello, World!';
console.log(message); // Outputs: 'Hello, World!'
})();
In this example, the function is immediately invoked after it’s defined, and message
is not accessible outside the function, thus not polluting the global scope.
How can I pass parameters to an IIFE in JavaScript?
You can pass parameters to an IIFE in JavaScript by including them in the parentheses that are used to invoke the function. Here’s an example:(function(name) {
console.log('Hello, ' + name);
})('World'); // Outputs: 'Hello, World'
In this example, the string ‘World’ is passed as a parameter to the IIFE.
Can a closure remember and update its local state in JavaScript?
Yes, a closure in JavaScript can remember and update its local state. This is because it retains access to its parent scope, including any variables defined in that scope, even after the parent function has finished execution. This means you can create functions with private state – the variables defined in the parent scope – and these variables can be updated each time the closure is invoked.
I am a web developer/designer from Bulgaria. My favorite web technologies include SVG, HTML, CSS, Tailwind, JavaScript, Node, Vue, and React. When I'm not programming the Web, I love to program my own reality ;)