자바스크립트의 유령, 클로저(Closure)와 스코프: 메모리 속에 살아있는 변수의 비밀

현대 웹 애플리케이션의 복잡도가 높아지면서 자바스크립트는 단순한 스크립트 언어를 넘어 정교한 상태 관리가 필요한 언어로 진화했습니다. 그 중심에는 개발자를 가장 괴롭히면서도, 가장 강력한 무기가 되어주는 **스코프(Scope)**와 **클로저(Closure)**가 있습니다. 이 개념들은 단순한 문법적 특징이 아니라, 자바스크립트 엔진이 데이터를 기억하고 보호하는 근본적인 방식입니다.
1. 스코프(Scope): 변수가 숨 쉴 수 있는 범위
스코프는 변수의 '생존 범위'이자 '접근 권한'을 결정하는 규칙입니다. 자바스크립트는 함수가 선언된 시점에 상위 스코프가 결정되는 렉시컬 스코프(Lexical Scope) 방식을 따릅니다.
중첩된 박스 구조로 이해하기
스코프를 이해하는 가장 쉬운 비유는 **'투명한 중첩 상자'**입니다.
- 내부 상자에 있는 사람은 외부 상자에 무엇이 있는지 볼 수 있습니다.
- 반대로 외부 상자에 있는 사람은 내부 상자 안의 내용물을 들여다볼 수 없습니다.
이처럼 하위 스코프에서 상위 스코프로 변수를 찾아 나가는 과정을 **스코프 체인(Scope Chain)**이라고 부릅니다.
2. 클로저(Closure): 함수가 끝난 뒤에도 남겨진 기억
클로저는 함수가 선언될 당시의 주변 환경(렉시컬 환경)을 기억하고, 함수가 외부에서 호출되어도 그 환경에 계속 접근할 수 있는 현상을 말합니다. 보통 함수 내부에서 함수를 반환할 때 발생하며, 이는 자바스크립트의 가비지 컬렉션(GC) 동작 방식과 밀접한 관련이 있습니다.
원래 함수 실행이 종료되면 내부 변수들은 메모리에서 해제되어야 합니다. 하지만 외부로 반환된 내부 함수가 해당 변수를 참조하고 있다면, 엔진은 "아직 이 데이터가 필요하구나"라고 판단하여 메모리에 유지시킵니다. 이것이 바로 클로저의 실체입니다.
3. 실전 Hands-on: 이커머스 장바구니 상태 관리
단순한 카운터 예제 대신, 실제 서비스에서 발생할 수 있는 '비공개 상태 관리' 로직을 클로저로 구현해 보겠습니다. 전역 변수 오염 없이 안전하게 데이터를 보호하는 것이 핵심입니다.
/**
* 고유한 장바구니 인스턴스를 생성하는 함수
* 클로저를 통해 cartItems 배열을 외부 접근으로부터 보호합니다.
*/
function createCart(userName) {
// 외부에서 직접 수정 불가능한 비공개 변수 (Encapsulation)
let cartItems = [];
return {
// 상품 추가 로직
addItem: function(product) {
cartItems.push(product);
console.log(`${userName}님의 장바구니에 [${product.name}] 추가됨.`);
},
// 전체 금액 계산 (고차 함수와 결합)
getTotalPrice: function() {
const total = cartItems.reduce((acc, cur) => acc + cur.price, 0);
return `${userName}님의 총 결제 금액: ${total}원`;
},
// 현재 장바구니 수량 확인
getItemCount: () => cartItems.length
};
}
// 사용 예시
const myCart = createCart("Alice");
myCart.addItem({ name: "맥북 프로", price: 2500000 });
myCart.addItem({ name: "매직 마우스", price: 99000 });
console.log(myCart.getTotalPrice()); // Alice님의 총 결제 금액: 2599000원
console.log(myCart.cartItems); // undefined (내부 변수 보호 성공)
코드 분석 및 트러블슈팅
- 캡슐화(Encapsulation): cartItems는 createCart 실행이 종료된 후에도 반환된 객체의 메서드들을 통해 메모리에 살아있습니다. 하지만 외부에서 myCart.cartItems로 직접 접근하는 것은 불가능합니다.
- 주의사항 (Memory Leak): 클로저는 참조하는 변수를 메모리에 계속 들고 있습니다. 수만 개의 클로저를 생성하고 해제하지 않으면 성능 저하의 원인이 될 수 있습니다. 더 이상 필요 없는 클로저는 참조를 null로 초기화하여 GC가 수거하도록 돕는 것이 좋습니다.
4. 트레이드오프(Trade-offs): 강력함 뒤의 비용
클로저는 자바스크립트의 강력한 도구이지만, 무분별한 사용은 지양해야 합니다.
| 장점 (Pros) | 단점 (Cons) |
| 데이터 은닉: 프라이빗 변수를 구현하여 의도치 않은 상태 변경 방지. | 메모리 소비: 참조가 유지되므로 일반 함수보다 메모리 점유율이 높음. |
| 모듈화: 전역 변수를 최소화하여 코드 간 충돌 방지. | 디버깅 난이도: 스코프 체인을 따라가며 변수 값을 추적하기 어려울 수 있음. |
| 상태 유지: 함수 호출 사이의 상태를 우아하게 보존. | 성능 저하: 깊은 스코프 체인 탐색 시 미세한 성능 오버헤드 발생. |
5. 결론: 클로저를 대하는 자세
클로저는 단순히 면접용 질문이 아닙니다. React의 useState 훅부터 고차 함수(Higher-order Function)까지, 현대 프레임워크의 근간을 이루는 철학입니다. 변수를 어디까지 공개할 것인지, 그리고 어떤 데이터를 메모리에 남길 것인지 결정하는 설계 역량이 곧 시니어 개발자의 실력을 좌우합니다.
지금 작성하고 있는 코드에서 전역 변수를 남발하고 있지는 않나요? 클로저를 활용해 데이터를 안전한 '상자' 안에 격리해 보는 것은 어떨까요?
혹시 클로저를 사용하면서 예상치 못한 메모리 누수를 경험했거나, 비동기 루프 속에서 var와 let의 스코프 차이 때문에 당황했던 에피소드가 있다면 그 지점이 바로 깊이 있는 학습이 시작되는 순간일 것입니다.