JavaScript Closure in Two Minutes
--
In JavaScript, closure refers to a function “remembering” the variables outside of it.
This behavior is exclusive to returned functions. Let’s have a look:
function whatDay(weekDay) {
return function saveDay() {
console.log(weekDay);
}
}
let today = whatDay("Saturday")
today() // Saturday
whatDay
is a function that returns a functionsaveDay
.- We assign
today
a function call ofwhatDay
with"Saturday"
as the argument forwhatDay
'sweekDay
parameter. saveDay
now closes over the outer variableweekDay
(whose value is"Saturday"
).today
is now permanently linked to the variable, even after it has "expired" in the outerwhatDay
function after its life cycle completes within the function's block.
This allows us to use the “remembered” variables at appropriate places in our code:
function today(weekDay) {
return function is(adjective) {
console.log(`${weekDay} is ${adjective}`);
}
}
const saturdayIs = today("Saturday")
const sundayIs = today("Sunday")
saturdayIs("great") // Saturday is great
sundayIs("better") // Sunday is better
Another example, this time without parameters:
function outer() {
let counter = 0;
function incrementCounter() {
counter++;
console.log(counter);
}
return incrementCounter;
}
const returnedIncrementCounter = outer();
returnedIncrementCounter(); // 1
returnedIncrementCounter(); // 2
Calls of returnedIncrementCounter
seem to increment counter
, even though the variable was not declared in incrementCounter
s block. If we didn't know about closure, we might assume that calling it outside of outer
's execution context would throw an error, because counter
would be undefined.
But what happens is that the returned function ( returnedIncrementCounter
in this case) closes over its own private copy of counter
— entirely disconnected from the original in outer
— which it will now reference.
Closure has many practical applications in JavaScript, such as limiting the way or number of times a function can be called:
function authenticate() {
this.username = 'johndoe';
this.password = 'hashedAndSalted';
let loginAttempts = 0;
function login(username, password) {
if (loginAttempts >= 3) {
console.log('Sorry, too many attempts. Please contact support.');
return;
}
if (username !== this.username || password !== this.password) {
console.log('Sorry, incorrect username or password. Please try again.');
loginAttempts += 1;
} else {
console.log(`Login successful! Welcome, ${this.username}.`);
}
}
return login;
}
const userTriesToLogIn = authenticate();
userTriesToLogIn('janedoe', 'secret');
// Sorry, incorrect username or password. Please try again.
userTriesToLogIn('jamesdoe', 'verysecret');
// Sorry, incorrect username or password. Please try again.
userTriesToLogIn('johndoe', '1234567');
// Sorry, incorrect username or password. Please try again.
userTriesToLogIn('johndoe', 'hashed');
// Sorry, too many attempts. Please contact support.
const anotherUserTriesToLogIn = authenticate();
anotherUserTriesToLogIn('johndoe', 'hashed');
// Sorry, incorrect username or password. Please try again.
anotherUserTriesToLogIn('johndoe', 'hashedAndSalted');
// Login successful! Welcome, johndoe.
As we can see, each instance of login
starts fresh with its own persisted private copies of the relevant variables.
Pretty neat.