🔒 클로저란?
📖 정의
**클로저(Closure)**는 함수가 선언될 때의 렉시컬 환경(Lexical Environment)을 기억하여, 함수가 외부에서 실행되어도 그 환경에 접근할 수 있는 것을 말합니다. 간단히 말하면, 함수가 자신이 태어난 곳을 기억하는 것입니다.
🎯 비유로 이해하기
배낭 비유
클로저를 배낭에 비유하면:
엄마가 아이를 학교에 보낼 때
├─ 아이 = 함수
├─ 배낭 = 클로저
├─ 배낭 속 물건 = 외부 변수
└─ 학교 = 실행 컨텍스트
과정:
1. 엄마(외부 함수)가 아이(내부 함수)에게 배낭을 줌
2. 배낭에는 필요한 물건들(변수)이 들어있음
3. 아이는 학교(다른 곳)에 가서도
4. 배낭 속 물건을 꺼내 쓸 수 있음
5. 엄마가 없어도 배낭은 계속 있음
클로저도 마찬가지:
- 함수가 선언될 때 "배낭"을 받음
- 나중에 어디서 실행되든
- 배낭 속 변수에 접근 가능
- 외부 함수가 종료되어도 유지됨
은행 금고 비유
은행 금고 시스템
├─ 금고실 = 외부 함수
├─ 금고 = 외부 변수 (비밀 데이터)
├─ 열쇠 = 내부 함수 (접근 방법)
└─ 고객 = 외부 코드
특징:
1. 금고실이 문 닫혀도 (함수 종료)
2. 열쇠를 가진 사람은 (클로저)
3. 여전히 금고에 접근 가능 (변수 접근)
4. 다른 사람은 접근 불가 (캡슐화)
코드로:
function createVault(secret) { // 금고실
let money = secret; // 금고
return {
deposit(amount) { // 입금 열쇠
money += amount;
},
getBalance() { // 조회 열쇠
return money;
}
};
}
const myVault = createVault(1000);
myVault.deposit(500);
console.log(myVault.getBalance()); // 1500
// money에 직접 접근 불가! (보안)
사진첩 비유
사진 찍기
├─ 카메라 = 외부 함수
├─ 사진 = 클로저
├─ 배경/사람 = 변수들
└─ 현상된 사진 = 반환된 함수
과정:
1. 특정 시점에 사진 찍음 (함수 선언)
2. 그 순간의 모든 것이 사진에 담김 (변수 캡처)
3. 나중에 사진을 보면 (함수 실행)
4. 그때의 상황을 볼 수 있음 (변수 접근)
5. 실제 그 장소가 사라져도 (외부 함수 종료)
6. 사진은 영구히 남아있음 (클로저 유지)
예시:
function takePhoto(location, people) {
return function viewPhoto() {
console.log(`${location}에서 ${people}와 함께`);
};
}
const photo1 = takePhoto('제주도', '가족');
const photo2 = takePhoto('서울', '친구들');
// 나중에 어디서든
photo1(); // 제주도에서 가족과 함께
photo2(); // 서울에서 친구들과 함께
⚙️ 작동 원리
1. 렉시컬 스코프
// 스코프: 변수의 유효 범위
// 전역 스코프
let globalVar = '전역';
function outer() {
// outer 스코프
let outerVar = '외부';
function inner() {
// inner 스코프
let innerVar = '내부';
// inner는 모든 변수에 접근 가능
console.log(innerVar); // '내부'
console.log(outerVar); // '외부'
console.log(globalVar); // '전역'
}
// outer는 inner의 변수에 접근 불가
// console.log(innerVar); // ❌ 에러!
inner();
}
outer();
// 렉시컬 스코프 체인:
// inner → outer → global
// 안쪽에서 바깥쪽으로 찾아감
// 스코프는 선언 위치에 의해 결정됨 (정적)
let x = 1;
function foo() {
console.log(x);
}
function bar() {
let x = 2;
foo(); // 1 출력 (foo가 선언된 곳의 x)
}
bar();
2. 클로저 기본 예시
// 가장 간단한 클로저
function makeCounter() {
let count = 0; // 외부 함수의 변수
return function() { // 내부 함수 (클로저)
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count는 counter 함수 안에 "살아있음"!
// makeCounter가 실행 완료되어도 count는 사라지지 않음
// 설명:
// 1. makeCounter 실행 → count = 0 생성
// 2. 내부 함수 반환
// 3. makeCounter 종료 (하지만 count는 유지!)
// 4. counter() 호출 시 count에 접근 가능
// 5. count는 counter 함수만 접근 가능 (캡슐화)
// 여러 개의 독립적인 클로저
const counter1 = makeCounter();
const counter2 = makeCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 (독립적!)
console.log(counter2()); // 2
console.log(counter1()); // 3 (영향 없음)
3. 클로저의 실행 컨텍스트
// 실행 컨텍스트와 클로저
function outer(x) {
// outer 실행 컨텍스트 생성
// { x: 10 }
return function inner(y) {
// inner 실행 컨텍스트 생성
// { y: 5 }
// + outer의 컨텍스트 참조 (클로저)
return x + y;
};
}
const addToTen = outer(10);
// outer 실행 완료
// 하지만 { x: 10 }은 메모리에 유지됨
// addToTen이 참조하고 있기 때문
console.log(addToTen(5)); // 15
console.log(addToTen(20)); // 30
// 메모리 구조:
/*
Global Execution Context
├─ addToTen: function
│
Closure (addToTen)
├─ [[Environment]]: { x: 10 }
└─ function(y) { return x + y; }
addToTen(5) 실행 시:
├─ y: 5
├─ x: 10 (클로저에서 가져옴)
└─ return: 15
*/