"클로저가 뭐예요?"라는 질문에 한 문장으로 답할 수 있는가? 이 글은 클로저의 정의를 정확히 짚고, 그 정의에 들어간 용어들을 하나씩 풀어낸다.
1. 한 문장 정의
클로저(Closure) 는 함수와, 그 함수가 선언된 렉시컬 환경(lexical environment)의 조합이다.
MDN의 정의도 동일하다.
"A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment)."
즉 클로저는 단순한 함수가 아니라, "함수 + 그 함수가 기억하는 바깥 변수들" 이라는 한 묶음을 가리킨다. 이 정의에는 두 개의 용어가 숨어 있다 — 렉시컬 환경과, 그 환경을 결정하는 렉시컬 스코프. 이 둘을 알아야 정의가 완성된다.
2. 정의에 쓰인 용어 풀이
렉시컬 스코프 (Lexical Scope)
변수의 유효 범위가 "코드를 어디서 호출했는지"가 아니라 "어디에 작성했는지" 로 결정되는 규칙이다. 자바스크립트는 렉시컬 스코프를 따른다.
const x = 1;
function a() {
console.log(x); // 호출 위치와 무관, '작성된 위치' 기준으로 x를 찾는다
}
function b() {
const x = 100;
a();
}
b(); // 1 (100이 아님)
a는 b 안에서 호출됐지만, a가 작성된 위치의 바깥은 전역이므로 x는 1이다.
렉시컬 환경 (Lexical Environment)
함수가 만들어질 때 결정되는, 그 함수가 접근할 수 있는 변수들의 집합(과 바깥 환경에 대한 참조) 이다. 모든 함수는 생성 시점에 자신을 감싼 렉시컬 환경에 대한 참조([[Environment]])를 내부에 품는다.
스코프 체인 (Scope Chain)
변수를 찾을 때 자기 환경 → 바깥 환경 → 더 바깥 환경 순으로 거슬러 올라가며 탐색하는 연결이다. 이 체인 덕분에 안쪽 함수가 바깥 변수를 읽을 수 있다.
→ 정리하면: 함수는 "내가 어디에 작성됐는지(렉시컬 스코프)"에 따라 "접근 가능한 변수 묶음(렉시컬 환경)"을 참조로 들고 다니고, 변수를 찾을 땐 그 참조를 따라(스코프 체인) 올라간다. 이 참조를 들고 다니는 함수가 바로 클로저다.
3. 그림으로 보는 구조
makeCounter()가 반환한 함수는 자기 안에 count가 없다. 하지만 [[Environment]] 참조를 통해 바깥 makeCounter 환경의 count를 스코프 체인으로 찾는다. 그래서 makeCounter()의 실행이 끝나도 count는 사라지지 않는다 — 반환된 함수(클로저)가 그 환경을 계속 참조하고 있기 때문이다.
4. 코드로 확인
function makeCounter() {
let count = 0; // makeCounter의 렉시컬 환경에 속한 변수
return function () {
return ++count; // 바깥 환경의 count를 참조 = 클로저
};
}
const counter = makeCounter(); // makeCounter는 여기서 실행 종료
counter(); // 1
counter(); // 2
counter(); // 3 — count가 계속 살아있다
makeCounter()는 이미 반환됐는데도 count가 1, 2, 3으로 누적된다. 반환된 함수가 count를 참조하는 클로저이기 때문이다.
5. "왜 변수가 안 사라지나" — GC 관점
자바스크립트의 가비지 컬렉터는 어디서도 참조되지 않는 값만 회수한다. 보통 함수가 끝나면 그 지역 변수도 함께 회수 대상이 된다.
하지만 클로저가 그 변수를 참조하고 있으면 여전히 도달 가능한(reachable) 상태라 회수되지 않는다. 그래서 바깥 함수가 종료된 뒤에도 변수가 살아남는다. (이 성질을 모르고 큰 객체를 오래 붙들면 메모리 누수가 되기도 한다.)
6. 정의를 구성하는 3요소 요약
| 요소 | 의미 |
|---|---|
| 함수 | 클로저의 본체. 바깥 변수를 참조하는 안쪽 함수 |
| 렉시컬 환경 | 그 함수가 선언될 때 접근 가능한 변수들의 묶음 |
| 참조 유지 | 함수가 살아있는 한 그 환경도 함께 살아있음 (그래서 변수가 유지됨) |
한 줄 정리 — 클로저는 "함수와, 그 함수가 선언된 렉시컬 환경의 조합"이다. 함수가 바깥 변수를 참조하면 그 변수는 함수와 함께 살아남고, 이를 통해 상태 유지와 데이터 은닉이 가능해진다.
클로저가 실무에서 일으키는 함정(React에서 옛 state에 갇히는 stale closure)은 클로저와 stale closure 글에서 이어서 다룬다.