12. Closures and Scope
A function's scope is determined by **where it was declared**, not where it was called.
What you'll learn
- 1Explain lexical scope.
- 2Know what a closure is and when one is created.
- 3Use a closure to build a counter with private state.
- 4Use the module pattern (IIFE) to split public from private members.
- 5Fix the classic loop-variable capture bug with `let`.
Overview
A function's scope is determined by **where it was declared**, not where it was called.
Core Concepts
1. Lexical scope
A function's scope is determined by **where it was declared**, not where it was called.
const x = 1;
function outer() {
const y = 2;
function inner() {
return x + y; // references the outer/global variable
}
return inner;
}2. Closures
A closure is what we call it when a function remembers the variables of the environment it was created in. Even after `outer()` returns, `inner` can still use `y`.
const fn = outer();
fn(); // 33. Private state: a counter
Closures let you create state that nothing outside the function can touch.
function makeCounter() {
let count = 0;
return {
inc() { count += 1; return count; },
get() { return count; },
};
}
const c = makeCounter();
c.inc(); c.inc(); c.get(); // 24. Module pattern (IIFE)
Immediately-invoked function expressions create a one-shot scope and return only the parts you want to expose.
const Logger = (() => {
let level = "info";
function setLevel(l) { level = l; }
function log(msg) { console.log(`[${level}] ${msg}`); }
return { setLevel, log };
})();Examples
| File | What it covers |
|---|---|
| `01_scope.js` | Block/function scope, let/const/var |
| `02_closure.js` | Closure basics |
| `03_counter.js` | Counter with private state |
| `04_module_pattern.js` | IIFE module pattern |
src/01_scope.js
/**
* Scope
*/
"use strict";
const g = "global";
function outer() {
const o = "outer";
if (true) {
const b = "block";
console.log(g, o, b);
}
// console.log(b); // ReferenceError
}
outer();
// let is block-scoped
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log("i =", i), 0);
}
src/02_closure.js
/**
* Closure basics
*/
"use strict";
function makeGreeter(greeting) {
return function (name) {
return `${greeting}, ${name}!`;
};
}
const hi = makeGreeter("Hi");
const hello = makeGreeter("Hello");
console.log(hi("Sun"));
console.log(hello("Moon"));
src/03_counter.js
/**
* Counter with private state
*/
"use strict";
function makeCounter(start = 0) {
let count = start;
return {
inc() { return ++count; },
dec() { return --count; },
get() { return count; },
};
}
const c = makeCounter(10);
c.inc();
c.inc();
c.dec();
console.log("count =", c.get()); // 11
src/04_module_pattern.js
/**
* IIFE module pattern
*/
"use strict";
const Logger = (() => {
let level = "info";
function setLevel(l) { level = l; }
function log(msg) { console.log(`[${level}] ${msg}`); }
return { setLevel, log };
})();
Logger.log("start");
Logger.setLevel("warn");
Logger.log("careful");
Common Mistakes
- Capturing a `var` loop variable in a `setTimeout` callback and getting the wrong value — use `let`.
- Forgetting that closures keep their captured variables alive and GC can't reclaim them (mind large data).
- Trying to mutate a closure variable from outside (you can't).
- Confusing function declaration hoisting with expression hoisting.
- Shadowing an outer variable with one of the same name inside and getting confused.
FAQ
Q1. When is a closure created?
Whenever a function definition references a variable from its surrounding scope.
Q2. Are closures bad for performance?
Generally negligible — but holding on to large objects can leak memory.
Q3. How do `let` and `const` define block scope?
Their visibility is bounded by `{ ... }`.
Practice
- `homework_01.js` — Write `makeAdder(n)` returning a closure that adds `n`.
- `homework_02.js` — Write `makeAccount` with a private balance.
homework/README.md
## homework_01.js — Write `makeAdder(n)`. Calling it returns a function that takes `x` and returns `x + n`.
## homework_02.js — Write `makeAccount(initial)`.
- Expose `deposit(amount)`, `withdraw(amount)`, `balance()`.
- The balance must not be reachable from outside.
homework/answer/homework_01.js
/**
* Homework 1: makeAdder
*/
"use strict";
/**
* @param {number} n
*/
function makeAdder(n) {
return (x) => x + n;
}
const add5 = makeAdder(5);
console.log(add5(3)); // 8
console.log(add5(10)); // 15
homework/answer/homework_02.js
/**
* Homework 2: account with private balance
*/
"use strict";
/**
* @param {number} initial
*/
function makeAccount(initial) {
let balance = initial;
return {
deposit(amount) { balance += amount; },
withdraw(amount) {
if (amount > balance) throw new Error("insufficient");
balance -= amount;
},
balance() { return balance; },
};
}
const acc = makeAccount(1000);
acc.deposit(500);
acc.withdraw(300);
console.log(acc.balance()); // 1200
Next Lecture
[13_Classes](../13_클래스/) — OOP with class syntax.
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗