🟨 2-18. 클로저(Closure) 완전 정복 — 함수형 자바스크립트의 핵심 개념과 실무 응용
1. 클로저란 무엇인가
MDN에서는 이렇게 정의한다 👇
“클로저는 함수와 그 함수가 선언될 때의 렉시컬 환경(Lexical Environment) 의 조합이다.”
즉, 함수가 자신이 선언된 스코프(환경)를 기억하는 현상이 바로 클로저다.
쉽게 말해,
“함수가 태어난 그 순간의 기억을 평생 가지고 있는 것.”
2. 기본 예시
function outer() {
const name = "기범";
function inner() {
console.log(name);
}
return inner;
}
const fn = outer();
fn(); // 기범
✅ outer()가 끝나서 보통이라면 name 변수는 사라져야 한다.
✅ 하지만 inner()가 name을 기억하고 있음 → 이것이 클로저!
3. 왜 클로저가 만들어질까?
자바스크립트는 렉시컬 스코프(Lexical Scope) 를 따르기 때문이다.
즉, 함수가 “호출된 위치”가 아니라 “선언된 위치”를 기준으로
변수의 유효 범위를 결정한다.
const x = 1;
function outer() {
const x = 2;
function inner() {
console.log(x);
}
return inner;
}
const fn = outer();
fn(); // 2
✅ inner()는 자신이 “outer 안에서” 정의됐기 때문에
outer의 변수 x=2를 기억하고 있다.
4. 클로저의 실전 활용 — 데이터 은닉
클로저는 정보를 숨기고 제어하는 데 자주 사용된다.
function createCounter() {
let count = 0;
return {
increase() {
count++;
console.log(count);
},
decrease() {
count--;
console.log(count);
},
};
}
const counter = createCounter();
counter.increase(); // 1
counter.increase(); // 2
counter.decrease(); // 1
✅ count 변수는 외부에서 직접 접근 불가
✅ 오직 increase, decrease 함수를 통해서만 조작 가능
➡ 클래스 없이도 캡슐화(Encapsulation) 구현 가능!
5. 클로저와 메모리 관리
클로저는 변수를 “기억”하기 때문에
불필요한 메모리 점유가 발생할 수도 있다.
function heavy() {
const bigData = new Array(1000000).fill("data");
return function () {
console.log(bigData.length);
};
}
const fn = heavy(); // bigData는 여전히 메모리에 남아있음
✅ 클로저가 참조 중인 변수는 GC(가비지 컬렉션) 대상에서 제외됨
✅ 따라서 불필요한 클로저는 제거하거나 null 처리해야 함
fn = null; // 참조 해제 → bigData 가비지 컬렉션 가능
6. 클로저와 루프
아래 코드를 보면 자주 발생하는 함정이 있다 👇
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
출력 결과:
3
3
3
이유는?
var는 함수 스코프라서, 모든 함수가 동일한 i를 공유하기 때문.
해결 방법 2가지 👇
✅ 1) let 사용 (블록 스코프)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// 0 1 2
✅ 2) 즉시 실행 함수 (IIFE)
for (var i = 0; i < 3; i++) {
((n) => setTimeout(() => console.log(n), 1000))(i);
}
클로저를 이용해 각 루프의 i 값을 고유하게 캡처한 것이다.
7. 클로저로 상태 유지하기
클로저는 비동기 함수에서도 상태를 유지하는 데 유용하다.
function delayedPrinter() {
let count = 0;
return function () {
setTimeout(() => {
count++;
console.log(`출력 횟수: ${count}`);
}, 500);
};
}
const print = delayedPrinter();
print(); // 출력 횟수: 1
print(); // 출력 횟수: 2
✅ count는 외부에서 접근 불가하지만,
✅ 함수 호출 시마다 내부적으로 상태가 누적된다.
8. 클로저와 함수형 프로그래밍
클로저는 함수형 프로그래밍의 핵심이기도 하다.
불변성과 순수 함수 개념을 유지하면서,
“상태를 함수 안에 숨긴다.”
function makeAdder(x) {
return function (y) {
return x + y;
};
}
const add5 = makeAdder(5);
console.log(add5(10)); // 15
✅ x=5라는 환경이 클로저에 저장됨
✅ add5는 5를 더하는 함수를 영구히 기억한다
➡ 이런 패턴은 React의 hook, Redux의 reducer 등에서도 자주 등장한다.
9. 실무에서 자주 쓰이는 클로저 패턴
목적 예시
| 상태 유지 | 카운터, 타이머, 애니메이션 상태 |
| 정보 은닉 | private 변수, 데이터 보호 |
| 함수 팩토리 | makeAdder, createValidator |
| 이벤트 핸들러 | 특정 컨텍스트를 유지하는 콜백 |
| React Hooks 내부 구현 | useState, useEffect 등 상태 저장 로직 |
예를 들어, React의 useState도 내부적으로 클로저를 활용한다.
function useState(initialValue) {
let state = initialValue;
function setState(newValue) {
state = newValue;
render();
}
return [state, setState];
}
✅ 외부에서 state에 직접 접근 불가
✅ setState로만 변경 가능 — 완벽한 캡슐화 구조
10. 디버깅 시 주의할 점
개발자 도구에서 클로저를 디버깅하면
변수가 사라지지 않고 남아 있는 것을 볼 수 있다.
✅ 크롬 DevTools → Sources → Scope → Closure 영역 확인
불필요한 참조가 많을수록 메모리 누수 가능성이 커진다.
→ 특히 이벤트 리스너나 타이머 내부 클로저는 주의!
element.addEventListener("click", () => {
console.log(heavyData);
});
// 해제하지 않으면 heavyData 계속 유지됨
element.removeEventListener("click", handler);
11. 정리
개념 설명
| 클로저 | 함수가 자신이 선언된 환경(변수)을 기억하는 현상 |
| 핵심 원리 | 렉시컬 스코프 |
| 주요 특징 | 상태 유지, 정보 은닉, 함수형 패턴 |
| 장점 | 데이터 보호, 모듈화, 재사용성 |
| 주의점 | 메모리 누수 가능성, 과도한 사용 지양 |
12. 마무리
클로저는 함수형 자바스크립트의 핵심이며,
React, Vue, Node 등 모든 환경에서 사용되는 개념이야.
이걸 제대로 이해하면, “왜 이렇게 동작하지?”라는 의문이 대부분 해결된다.
“클로저를 이해하면 자바스크립트가 단순한 스크립트 언어에서
구조적인 언어로 보이기 시작한다.”