Summary: in this tutorial, you will learn about JavaScript closures and how to use closures in your code more effectively.
Introduction to JavaScript closures
In JavaScript, a closure is a function that references variables in the outer scope from its inner scope. The closure preserves the outer scope inside its inner scope.
To understand the closures, you need to know how the lexical scoping works first.
Lexical scoping
Lexical scoping defines the scope of a variable by the position of that variable declared in the source code. For example:
let name = 'John';
function greeting() {
let message = 'Hi';
console.log(message + ' '+ name);
}
Code language: JavaScript (javascript)
In this example:
- The variable
name
is a global variable. It is accessible from anywhere including within thegreeting()
function. - The variable
message
is a local variable that is accessible only within thegreeting()
function.
If you try to access the message
variable outside the greeting()
function, you will get an error.
So the JavaScript engine uses the scope to manage the variable accessibility.
According to lexical scoping, the scopes can be nested and the inner function can access the variables declared in its outer scope. For example:
function greeting() {
let message = 'Hi';
function sayHi() {
console.log(message);
}
sayHi();
}
greeting();
Code language: JavaScript (javascript)
The greeting()
function creates a local variable named message
and a function named sayHi()
.
The sayHi()
is the inner function that is available only within the body of the greeting()
function.
The sayHi()
function can access the variables of the outer function such as the message
variable of the greeting()
function.
Inside the greeting()
function, we call the sayHi()
function to display the message Hi
.
JavaScript closures
Let’s modify the greeting()
function:
function greeting() {
let message = 'Hi';
function sayHi() {
console.log(message);
}
return sayHi;
}
let hi = greeting();
hi(); // still can access the message variable
Code language: JavaScript (javascript)
Now, instead of executing the sayHi()
function inside the greeting()
function, the greeting()
function returns the sayHi()
function object.
Note that functions are the first-class citizens in JavaScript, therefore, you can return a function from another function.
Outside of the greeting()
function, we assigned the hi
variable the value returned by the greeting()
function, which is a reference to the sayHi()
function.
Then we executed the sayHi()
function using the reference of that function: hi()
. If you run the code, you will get the same effect as the one above.
However, the interesting point here is that, typically, a local variable only exists during the function’s execution.
It means that when the greeting()
function has completed executing, the message
variable is no longer accessible.
In this case, we execute the hi()
function that references the sayHi()
function, the message
variable still exists.
The magic of this is closure. In other words, the sayHi()
function is a closure.
A closure is a function that preserves the outer scope in its inner scope.
More JavaScript Closure examples
The following example illustrates a more practical example of closure.
function greeting(message) {
return function(name){
return message + ' ' + name;
}
}
let sayHi = greeting('Hi');
let sayHello = greeting('Hello');
console.log(sayHi('John')); // Hi John
console.log(sayHello('John')); // Hello John
Code language: JavaScript (javascript)
The greeting()
function takes one argument named message
and returns a function that accepts a single argument called name
.
The return function returns a greeting message that is a combination of the message
and name
variables.
The greeting()
function behaves like a function factory. It creates sayHi()
and sayHello()
functions with the respective messages Hi
and Hello
.
The sayHi()
and sayHello()
are closures. They share the same function body but store different scopes.
In the sayHi()
closure, the message
is Hi
, while in the sayHello()
closure the message
is Hello
.
JavaScript closures in a loop
Consider the following example:
for (var index = 1; index <= 3; index++) {
setTimeout(function () {
console.log('after ' + index + ' second(s):' + index);
}, index * 1000);
}
Code language: JavaScript (javascript)
Output
after 4 second(s):4
after 4 second(s):4
after 4 second(s):4
Code language: CSS (css)
The code shows the same message.
What we wanted to do in the loop is to copy the value of i
in each iteration at the time of iteration to display a message after 1, 2, and 3 seconds.
The reason you see the same message after 4 seconds is that the callback passed to the setTimeout()
a closure. It remembers the value of i
from the last iteration of the loop, which is 4.
In addition, all three closures created by the for-loop share the same global scope access the same value of i
.
To fix this issue, you need to create a new closure scope in each iteration of the loop.
There are two popular solutions: IIFE & let
keyword.
1) Using the IIFE solution
In this solution, you use an immediately invoked function expression (a.k.a IIFE) because an IIFE creates a new scope by declaring a function and immediately executing it.
for (var index = 1; index <= 3; index++) {
(function (index) {
setTimeout(function () {
console.log('after ' + index + ' second(s):' + index);
}, index * 1000);
})(index);
}
Code language: JavaScript (javascript)
Output
after 1 second(s):1
after 2 second(s):2
after 3 second(s):3
Code language: CSS (css)
2) Using let keyword in ES6
In ES6, you can use the let
keyword to declare a variable that is block-scoped.
If you use the let
keyword in the for-loop, it will create a new lexical scope in each iteration. In other words, you will have a new index
variable in each iteration.
In addition, the new lexical scope is chained up to the previous scope so that the previous value of the index
is copied from the previous scope to the new one.
for (let index = 1; index <= 3; index++) {
setTimeout(function () {
console.log('after ' + index + ' second(s):' + index);
}, index * 1000);
}
Code language: JavaScript (javascript)
Output
after 1 second(s):1
after 2 second(s):2
after 3 second(s):3
Code language: CSS (css)
Summary
- Lexical scoping describes how the JavaScript engine uses the location of the variable in the code to determine where that variable is available.
- A closure is a combination of a function and its ability to remember variables in the outer scope.