🔒
함수형 · ES6
중급 · 선수: 11_배열_고차함수
12. 클로저와 스코프
함수의 스코프는 **선언된 위치** 에 의해 정해집니다. 호출 위치가 아닙니다.
클로저스코프렉시컬캡슐화
소요 시간
⏱ 약 1.5시간
난이도
📊 중급
선수 조건
🎯 단원 11
결과물
함수의 스코프는 **선언된 위치** 에 의해 정해집니다. 호출 위치가 아닙니다.
이 강의에서 배우는 것
- 1렉시컬 스코프(lexical scope) 의 의미를 설명한다.
- 2클로저가 무엇인지, 언제 생성되는지 안다.
- 3클로저로 비공개 상태(private state) 를 가진 카운터를 만든다.
- 4모듈 패턴(IIFE) 으로 공개/비공개 멤버를 분리한다.
- 5흔한 루프 변수 캡처 버그를 `let` 으로 해결한다.
소개
함수의 스코프는 **선언된 위치** 에 의해 정해집니다. 호출 위치가 아닙니다.
핵심 개념
1. 렉시컬 스코프
함수의 스코프는 **선언된 위치** 에 의해 정해집니다. 호출 위치가 아닙니다.
javascript
const x = 1;
function outer() {
const y = 2;
function inner() {
return x + y; // outer/전역 변수 참조
}
return inner;
}2. 클로저
함수가 자신이 만들어진 환경(변수)을 기억하는 현상이 클로저입니다. `outer()` 가 반환된 뒤에도 `inner` 는 `y` 를 사용할 수 있습니다.
javascript
const fn = outer();
fn(); // 33. 비공개 상태: 카운터
클로저로 외부에서 직접 접근할 수 없는 상태를 만들 수 있습니다.
javascript
function makeCounter() {
let count = 0;
return {
inc() { count += 1; return count; },
get() { return count; },
};
}
const c = makeCounter();
c.inc(); c.inc(); c.get(); // 24. 모듈 패턴 (IIFE)
즉시 실행 함수 표현식으로 한 번만 실행되는 스코프를 만들고, 공개할 멤버만 반환합니다.
javascript
const Logger = (() => {
let level = "info";
function setLevel(l) { level = l; }
function log(msg) { console.log(`[${level}] ${msg}`); }
return { setLevel, log };
})();핵심 예제
| 파일 | 다루는 내용 |
|---|---|
| `01_scope.js` | 블록/함수 스코프, let/const/var |
| `02_closure.js` | 클로저 기본 |
| `03_counter.js` | 비공개 상태 카운터 |
| `04_module_pattern.js` | IIFE 모듈 패턴 |
src/01_scope.js
javascript
/**
* 스코프
*/
"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 은 블록 스코프
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log("i =", i), 0);
}
src/02_closure.js
javascript
/**
* 클로저 기본
*/
"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
javascript
/**
* 비공개 상태 카운터
*/
"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
javascript
/**
* IIFE 모듈 패턴
*/
"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");
자주 하는 실수
- `var` 로 선언한 루프 변수를 setTimeout 콜백에서 잘못 캡처한다 → `let` 사용.
- 클로저가 메모리를 잡고 있어 GC 가 안 됨을 잊는다 (대량 데이터 주의).
- 클로저 변수를 외부에서 직접 바꾸려 한다 (불가능).
- `function` 선언 호이스팅과 표현식 호이스팅 차이를 혼동.
- 동일 이름의 외부 변수와 내부 변수 섀도잉으로 혼란.
FAQ
Q1. 클로저는 언제 만들어지나요?
함수가 정의되는 순간 외부 환경을 참조하면 클로저가 만들어집니다.
Q2. 클로저는 성능에 나쁜가요?
일반적으로 무시할 수준입니다. 다만 큰 객체를 잡고 있으면 메모리 누수 가능.
Q3. let 과 const 의 블록 스코프?
`{ }` 단위로 유효 범위가 제한됩니다.
과제
- `homework_01.js` — `makeAdder(n)` 을 만들어 더하기 클로저를 반환하세요.
- `homework_02.js` — 비공개 잔액을 가진 `makeAccount` 를 작성하세요.
homework/README.md
## homework_01.js `makeAdder(n)` 을 작성하세요. 호출하면 `x` 를 받아 `x + n` 을 반환하는 함수를 돌려줍니다.
## homework_02.js `makeAccount(initial)` 을 작성하세요.
- `deposit(amount)`, `withdraw(amount)`, `balance()` 를 노출
- 잔액은 외부에서 직접 접근 불가
homework/answer/homework_01.js
javascript
/**
* 과제 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
javascript
/**
* 과제 2: 비공개 잔액 계좌
*/
"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
다음 단원
[13_클래스](../13_클래스/) — class 문법으로 OOP.