Imagine a function packed with a little backpack. Wherever the function goes — stored in a variable, passed around, called much later — it carries that backpack, and inside are the variables from the place it was born. A closure is exactly this: a function together with the environment it was created in. It "closes over" the variables around it, keeping them alive even after the code that made them has finished running.
Closures are what make higher-order functions truly powerful. Once a function can remember values from where it was made, you can build function factories — functions that manufacture other, specialised functions on demand.
makeAdder has already returned and, you'd think,
Two calls to makeAdder made two independent closures, each with its own
private
Once you can bake a value into a function, you can stamp out families of related functions from one
template. Here a single
This is the same trick a configured function uses everywhere: set the knobs once, get back a ready-to-use function you can call again and again without re-passing the settings.
A closure can capture a variable it is allowed to change, giving you private state that
nothing outside can touch — a tiny bit of encapsulation without a class. Each call to
makeCounter gets its own hidden count:
Purists note this closure is stateful — calling tick() twice gives
different answers, so it isn't a pure function. That's fine: closures are a tool, and here we're
using them deliberately to hold state. Most of the time, though, you'll close over values you
only read, keeping things pure.
makeAdder(5) is add with its first argument already filled in.map callback can capture the surrounding data it needs, instead of having it passed
in awkwardly.
The famous closure gotcha is capturing a loop variable. A closure remembers the
variable, not a snapshot of its value at creation time — so if the variable keeps
changing, every closure sees the final value. With the old function-scoped
var, all the closures below share one i:
The fix is to give each iteration a fresh binding — which is exactly what block-scoped
let (or const) does. This one bug has bitten every JavaScript
programmer at least once; now it won't bite you.