클로저 (Closure)
1️⃣ 핵심 개념: 클로저란?
🔹 정의
클로저(Closure) 는 함수와 그 함수가 선언될 당시의 렉시컬 환경(Lexical Environment) 의 조합입니다. 즉, 클로저는 함수가 외부 스코프에 있는 변수에 접근할 수 있도록 해주는 구조입니다.
- 함수는 선언된 위치의 스코프를 기억합니다.
- 그 스코프의 변수는 그 스코프를 참조하는 함수 (클로저)가 도달 가능(reacheable) 한 동안 가비지 컬렉션 대상이 아닙니다
- "메모리에 영원히 남는다"가 아니라, 참조가 남아있을 때만 유지됩니다.
🧠 내부 동작(간단 버전)
- 엔진은 함수가 만들어질 때 **환경 레코드 (Environment Record)**를 묶습니다.
- 이 레코드는 스코프 체인의 한 고리로 남고, **함수 객체의 숨은 슬롯([[Scopes]])을 통해 참조됩니다.
- 내부 함수가 실행될 때 해당 체인을 거슬로 올라가 **자유 변수(외부 변수)**를 찾습니다.
🧐 간단한 예시
function outer() {
let count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}
const counter = outer();
counter(); // 1
counter(); // 2
outer()호출 시count가 생성되고,inner는count가 있는 렉시컬 환경을 캡처합니다.outer는 끝났지만counter가 살아있으므로count도 살아 있습니다. → 클로저 효과
2️⃣ 다양한 예제와 해설
🧐 예제 1: 여러 개의 클로저 인스턴스
function createCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter1 = createCounter();
const counter2 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1
counter1과counter2는 각자 다른 렉시컬 환경을 가집니다.
🧐 예제 2: 파라미터 캡처 (커링/부분 적용)
function makeMultiplier(multiplier) {
return function (number) {
return number * multiplier;
};
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
- 매개변수
multiplier는 외부 스코프 변수로 캡처되어 이후에도 사용됩니다.
🧐 예제 3: 루프와 클로저(고전 함정)
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs.push(function () {
console.log(i);
});
}
funcs[0](); // 3
funcs[1](); // 3
funcs[2](); // 3
var는 함수 스코프라서, 루프 종료 후의i(=3)를 모두 참조합니다.
✅ 해결 1: let 사용(블록 스코프)
const funcs = [];
for (let i = 0; i < 3; i++) {
funcs.push(function () {
console.log(i);
});
}
funcs[0](); // 0
funcs[1](); // 1
funcs[2](); // 2
✅ 해결 2: IIFE(즉시 실행 함수)로 스냅샷 캡처
const funcs = [];
for (var i = 0; i < 3; i++) {
(function (snapshot) {
funcs.push(function () {
console.log(snapshot);
});
})(i);
}
- IIFF로 매 반복마다 새로운 스코프와 별도의 snapshot 매개변수를 만들어 문제 해결
3️⃣ 실전 활용 패턴
🔹 데이터 은닉(캡슐화)
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
balance += amount;
return balance;
},
withdraw(amount) {
balance -= amount;
return balance;
},
getBalance() {
return balance;
},
};
}
const account = createBankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
balance외부에서 직접 접근 불가 공개 API로만 조작 → 안전한 상태 관리
🔹 once() 함수
function once(fn) {
let called = false;
let result;
return function (...args) {
if (!called) {
called = true;
result = fn.apply(this, args); // this/args 보존
}
return result;
};
}
const init = once(() => console.log("Initialized"));
init(); // Initialized
init(); // (아무 일 없음)