Chapter 4 · Real programs
Closures
Step 1 of 22 · Concept
Functions that remember
By the end of this lesson you can:
- make a function that keeps variables of its own between calls,
- make two counters that count apart,
- keep a balance that no code outside can change.
Step 2 of 22 · Concept
A function can outlive the call that made it
function makeCounter() {
let count = 0;
return () => {
count += 1;
return count;
};
}
const counter = makeCounter();
makeCounter() returns an arrow function that uses count. The call ends and its frame goes, but count lives on,
kept on the heap by that function. A function that keeps the variables around it is a closure.
Step 3 of 22 · Trace
Watch count survive
The frame of makeCounter comes and goes; watch its count. Press Next.
function makeCounter() {
let count = 0;
return () => {
count += 1;
return count;
};
}
const counter = makeCounter();
console.log(counter());
console.log(counter());makeCounter()runs:countis a box in its frame, 0. Line 3 makes the arrow function.makeCounterreturned the function, and its frame is gone.countis not: the function keeps it.counter()runs in a frame of its own. Line 4 added 1 to the keptcount. Line 5 returns it.- Line 9 printed
1, and the call's frame is gone. The keptcountis still 1. - The second call finds the same
countand makes it 2. - Line 10 printed
2. The program is finished.
Step 4 of 22 · Quiz
Predict the third call
Type the missing line.
function makeCounter() {
let count = 0;
return () => {
count += 1;
return count;
};
}
const counter = makeCounter();
console.log(counter());
console.log(counter());
console.log(counter());
Step 5 of 22 · Demo
See it run
Run it and compare with your answer.
Step 6 of 22 · Concept
Each call makes new variables
const c1 = makeCounter();
const c2 = makeCounter();
Each call of makeCounter gets a new frame, with a new count. So c1 keeps one count and c2 the other. They
count apart.
Step 7 of 22 · Quiz
One count or two?
What does this program print?
function makeCounter() {
let count = 0;
return () => {
count += 1;
return count;
};
}
const c1 = makeCounter();
const c2 = makeCounter();
console.log(c1(), c1(), c2(), c1());
Step 8 of 22 · Demo
See it run
Run it and compare with your answer. Then add a third counter, c3, and log c3().
Step 9 of 22 · Trace
Two counters, two boxes
Line 10 calls c1, c1, c2, then c1. Watch which count each call changes.
function makeCounter() {
let count = 0;
return () => {
count += 1;
return count;
};
}
const c1 = makeCounter();
const c2 = makeCounter();
console.log(c1(), c1(), c2(), c1());c1andc2each keep acountof their own, both 0.- The first
c1()added 1 toc1'scount: 1. - The second
c1()changes the same box again: 2. c2()changes onlyc2'scount, to 1.c1's stays 2.- The last
c1()makesc1'scount3. - Line 10 printed the four answers,
1 2 1 3. The program is finished.
Step 10 of 22 · Exercise
Your turn: a lap counter
Complete makeLapCounter(). Each call of the function it returns counts one more lap and returns
`Lap ${laps}`. The program must print:
Lap 1
Lap 2
Lap 3
Where let laps = 0 goes matters. Inside the arrow function, it starts again at every call: Lap 1 three times.
It belongs to makeLapCounter, which runs once.
In makeLapCounter: let laps = 0;, then return () => { ... };, as makeCounter does. In the arrow function: +=, then a
return with a template literal.
laps += 1;
return `Lap ${laps}`;
Step 11 of 22 · Concept
A function that returns a function
function makeAdder(n) {
return (x) => x + n;
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
makeAdder(5) gives a function that adds 5. A parameter is a variable of the call too, so the arrow function keeps n as
the counter kept count. add5 keeps an n of 5, add10 one of 10.
Step 12 of 22 · Quiz
What does add10 give?
Type the missing line.
function makeAdder(n) {
return (x) => x + n;
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
console.log(add5(1));
console.log(add10(1));
Step 13 of 22 · Demo
See it run
Run it and compare with your answer. Then make add100 and log add100(1).
Step 14 of 22 · Concept
A closure sees the variable, not a copy
let msg = "before";
const show = () => msg;
msg = "after";
console.log(show());
This prints after. show keeps the variable msg itself, not the text it held when show was made. So it reads
what the box holds when it runs.
A callback that uses a variable around it is a closure too. Lesson 12's scores.filter((s) => s >= limit) reads
limit itself, each time it runs.
Step 15 of 22 · Quiz
One i or three?
Each round pushes an arrow function that returns i. What does this program print?
const fns = [];
for (let i = 0; i < 3; i++) {
fns.push(() => i);
}
console.log(fns.map((f) => f()));
Step 16 of 22 · Demo
See it run
Run it and compare with your answer: let in a for makes a new variable each round.
Step 17 of 22 · Exercise
Your turn: make a multiplier
Write makeMultiplier(n). It returns a function that multiplies its argument by n. The program must print:
8 12
return x * n; does not return a function: it computes at once, and x is not there yet. Node stops with
ReferenceError: x is not defined. Return an arrow function that takes x.
Write it as makeAdder is written, with * in place of +: a function with the parameter n, whose return gives
an arrow function.
return (x) => x * n;
Step 18 of 22 · Concept
Variables nobody else can reach
function makeAccount() {
let balance = 0;
const deposit = (amount) => {
balance += amount;
};
const getBalance = () => balance;
return { deposit, getBalance };
}
const account = makeAccount();
makeAccount returns { deposit, getBalance } (lesson 11's shorthand). Both functions keep the one balance. No
property holds it, so only these two functions can reach it.
Step 19 of 22 · Demo
The balance is out of reach
Run it. account.balance is undefined: the object has no such property. console.log shows only the two
functions.
Step 20 of 22 · Check yourself
Check yourself: two scoreboards
No shape this time.
makeScoreboard()keeps a privatetotal, starting at 0.- It returns
{ add, getTotal }:add(points)adds tototal,getTotal()gives it. - Make one scoreboard for Ada and one for Bo. Ada scores 10, then 5; Bo scores 7. Print both totals.
The output must be:
Ada: 15
Bo: 7
It has the shape of makeAccount: one let, two arrow functions, one return of the shorthand object. Two calls
of it give two totals that count apart.
Step 21 of 22 · Challenge · optional
Challenge: twice
Optional.
Write twice(fn). It returns a function that calls fn two times: twice(double)(3) is double(double(3)). The
program must print:
12
21
twice returns a function, as makeAdder does. The function it returns keeps fn, a parameter, as the adder kept
n.
fn holds a function, so it can be called: fn(x). Calling it on its own answer, fn(fn(x)), calls it twice.
return (x) => fn(fn(x));
Step 22 of 22 · Recap
Recap
- A function made inside a call keeps that call's variables after the call returns: a closure.
- Each call makes new variables, so
c1andc2count apart. - A closure sees the variable, not a copy;
letin aformakes a new one each round. - A callback that uses a variable around it, such as
xs.filter((x) => x > limit), is a closure.
Next: lesson 16 shares one method between many objects: prototypes.
Review
4 questions from this lesson, in a new order each time. Answer from memory: 3 of 4 right on the first try passes. On a day after you finish the lesson, a pass earns 15 XP.
The review needs JavaScript.
function makeCounter() {
let count = 0;
return () => {
count += 1;
return count;
};
}
const counter = makeCounter();
console.log(counter());
console.log(counter());
console.log(counter());
function makeCounter() {
let count = 0;
return () => {
count += 1;
return count;
};
}
const c1 = makeCounter();
const c2 = makeCounter();
console.log(c1(), c1(), c2(), c1());
function makeLapCounter() {
// keep laps here, then return an arrow function
// that adds 1 to laps and returns the text
}
const lap = makeLapCounter();
console.log(lap());
console.log(lap());
console.log(lap());
function makeAdder(n) {
return (x) => x + n;
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
console.log(add5(1));
console.log(add10(1));
const fns = [];
for (let i = 0; i < 3; i++) {
fns.push(() => i);
}
console.log(fns.map((f) => f()));
// makeMultiplier(n): return a function
// that multiplies its argument by n
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(4), triple(4));
function makeAccount() {
let balance = 0;
const deposit = (amount) => {
balance += amount;
};
const getBalance = () => balance;
return { deposit, getBalance };
}
const account = makeAccount();
account.deposit(10);
account.deposit(5);
console.log(account.getBalance());
console.log(account.balance);
console.log(account);
// 1. makeScoreboard() keeps a private total
// 2. it returns { add, getTotal }
// 3. one for Ada (10, then 5), one for Bo (7)
const double = (x) => x * 2;
const addTen = (x) => x + 10;
// twice(fn): return a function that
// calls fn two times on its argument
console.log(twice(double)(3));
console.log(twice(addTen)(1));