Now we get to one of the most powerful (and famous) ideas in JavaScript. It has a slightly scary name, closures, but the idea is surprisingly simple once it clicks.
Here's the key fact: a function remembers the variables from the place where it was created, even after that place is gone.
Let's see it in action:
function makeCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter());
console.log(counter());
console.log(counter());
It prints
1 2 3
Let's slow down and think about how strange this is.
- We call
makeCounter(). It creates a local variablecount = 0, and then returns a little inner function. ThenmakeCounteris finished. - Normally, when a function finishes, its local variables disappear.
countshould be gone! - But when we call
counter(), it happily adds one tocountand returns it. And it keeps remembering the value between calls: 1, 2, 3.
The inner function has closed over the variable count. It carries count around with it, wherever it goes. That combination (a function plus the variables it remembers) is called a closure.
When a function is created inside another function, it packs a backpack containing all the variables it can see at that moment. When the outer function finishes, the inner function walks away wearing its backpack. Whenever it runs later, it can reach into the backpack and use (or change) those variables. No one else can get into the backpack. Only that function.
Every call makes a new backpack
Each time you call makeCounter, you get a brand-new count and a brand-new function to go with it. They're completely independent:
const counterA = makeCounter();
const counterB = makeCounter();
console.log(counterA());
console.log(counterA());
console.log(counterA());
console.log(counterB());
It prints
1 2 3 1
counterA and counterB each have their own backpack, with their own count inside. Counting with one doesn't affect the other.
Why closures are useful
1. Private data
Notice that there's no way to change count from outside, except by calling the counter. You can't write counter.count = 1000. The variable is completely private. This is a great way to protect data from being messed with:
function createBankAccount(startingBalance) {
let balance = startingBalance;
return {
deposit(amount) {
balance += amount;
return balance;
},
withdraw(amount) {
if (amount > balance) {
console.log("❌ Not enough money!");
return balance;
}
balance -= amount;
return balance;
},
getBalance() {
return balance;
},
};
}
const myAccount = createBankAccount(100);
console.log(myAccount.deposit(50));
console.log(myAccount.withdraw(500));
console.log(myAccount.withdraw(30));
console.log(myAccount.getBalance());
console.log(myAccount.balance);
It prints
150 ❌ Not enough money! 150 120 120 undefined
The balance variable is locked safely in the backpack of those three methods. The only way to change it is through deposit and withdraw, which follow the rules. myAccount.balance doesn't exist, so nobody can cheat and give themselves a million dollars.
2. Function factories
A function that creates and returns other functions is sometimes called a factory. Closures let each factory-made function remember its own settings:
function makeGreeter(greeting) {
return (name) => `${greeting}, ${name}!`;
}
const sayHello = makeGreeter("Hello");
const sayAkwaaba = makeGreeter("Welcome");
const sayYo = makeGreeter("Yo");
console.log(sayHello("Mia"));
console.log(sayAkwaaba("Omar"));
console.log(sayYo("Professor"));
It prints
Hello, Mia! Welcome, Omar! Yo, Professor!
function makeMultiplier(factor) {
return (number) => number * factor;
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
const tenTimes = makeMultiplier(10);
console.log(double(5), triple(5), tenTimes(5));
It prints
10 15 50
Each function remembers the factor it was made with.
3. Remembering things between clicks
When you start building web pages in Grades 11 and 12, you'll write functions that run whenever someone clicks a button. Closures are how those functions remember things (like how many times the button has been clicked) without using messy global variables.
Here's a secret: every function in JavaScript is a closure. Whenever a function uses a variable from an outer scope (like showHeader using the global appName earlier in this grade), it's closing over that variable. Closures only feel special when the inner function outlives the outer function, like our counter. But the mechanism is exactly the same: functions can always see the variables from the place they were created.