🔗
클로저와 스코프 체인 — 함수가 외부 변수를 기억하는 원리
Lexical Environment 객체가 만드는 스코프 체인의 내부 구조
function makeCounter() {
let count = 0;
return function() {
return ++count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2 — count가 살아 있다
makeCounter()는 이미 반환됐는데, 내부 함수가 count를 계속 쓸 수 있다. 이게 클로저.
V8 내부 — Lexical Environment
모든 함수 실행 시 Lexical Environment 객체가 생성된다. 여기에 로컬 변수와 외부 환경(outer) 참조가 저장된다.
makeCounter() 호출 시:
LexicalEnvironment {
count: 0,
outer: → global LexicalEnvironment
}
반환된 익명 함수:
[[Environment]]: → makeCounter의 LexicalEnvironment ← 이게 클로저
익명 함수의 [[Environment]] 내부 슬롯이 makeCounter의 Lexical Environment를 참조한다. 이 참조가 살아 있는 한 count가 GC되지 않는다.
counter()를 호출하면: 자기 Lexical Environment에서 count를 찾고 → 없으면 outer를 따라 올라간다 → makeCounter의 환경에서 count를 찾는다. 이게 스코프 체인.
메모리 누수 주의
클로저가 외부 변수를 참조하면 그 환경이 GC되지 않는다. 이벤트 리스너에 클로저를 등록하고 해제 안 하면 메모리 누수.
// ❌ 누수 — element가 제거되어도 handler가 bigData를 잡고 있다
const bigData = new Array(1000000);
element.addEventListener('click', () => console.log(bigData.length));
// ✅ 해제
const handler = () => console.log(bigData.length);
element.addEventListener('click', handler);
element.removeEventListener('click', handler); // 해제 → bigData GC 가능
핵심 포인트
1
함수 생성 시 [[Environment]] 슬롯에 현재 Lexical Environment가 저장된다
2
변수 참조 시 자기 환경 → outer → outer... 순으로 스코프 체인을 따라간다
3
외부 변수를 참조하는 함수가 살아 있으면 그 Lexical Environment는 GC되지 않는다
4
이벤트 리스너의 클로저는 반드시 removeEventListener로 해제
사용 사례
데이터 은닉 — 클로저로 private 변수 구현 (모듈 패턴)
커링/부분 적용 — 인자를 미리 고정하는 함수 생성