[JavaScript] Closure 클로저

JS를 공부하다 보면 클로저는 항상 중요하게 다뤄지기도 하고 단골처럼 어렵다고 느껴지는 개념이라 늘 좀 어렵게 접근 하게 되는 것 같습니다..!
그리고 면접에서도 자주 물어보는 주제이기도 하구요ㅎㅎ
하지만,, 클로저를 약간 감으로는 알아도 말로 설명해보라고 하면 말이 잘 안 나오는 것 같아서 한 번 정리해보고자 합니다!
1️⃣ 클로저란?
MDN에서의 정의는 이렇습니다
클로저는 함수와 그 함수가 선언된 어휘적 환경의 조합이다.
…?
말이 너무 어렵죠...?
그래서 더 쉽게 말해보면!
“외부 함수보다 오래 살아남는 내부 함수”가 클로저다!
즉, 함수 안에 함수를 만들고 그 내부 함수가 외부 함수의 변수를 기억해서 나중에 사용할 수 있다면 클로저라고 할 수 있습니다!
2️⃣ 예제로 이해해보기
function outer() {
let count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}
const myFunc = outer();
myFunc(); // 1
myFunc(); // 2
myFunc(); // 3
이 코드에서 outer()는 호출되자마자 inner 함수를 반환하고 끝납니다. 그런데 myFunc()를 호출할 때마다 count 값이 계속 증가합니다.
왜 그럴까요?
outer()는 이미 실행이 끝났는데, 그 안의 count는 계속 살아있죠. 바로 이때 count를 기억하고 있는 inner() 함수가 클로저인 거입니다!
3️⃣ 클로저가 유용한 이유?
✅ 데이터를 보호하면서 상태를 유지할 수 있다!
예를 들어, 간단한 카운터 모듈을 만들어서 설명드려보겠습니다!
function createCounter() {
let count = 0;
return {
increase() {
count++;
return count;
},
decrease() {
count--;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increase()); // 1
console.log(counter.increase()); // 2
console.log(counter.getCount()); // 2
console.log(counter.decrease()); // 1
위 코드에서 count는 외부에서 직접 접근할 수 없습니다.
increase, decrease, getCount를 통해서만 값을 변경하거나 읽을 수 있죠. 이런 식으로 클로저를 이용하면 전역 변수 없이도 상태를 안전하게 관리할 수 있고 이런 부분 때문에 많이 사용하게 되는 것 이죠!
4️⃣ 주의 사항
다만 헷갈리지 마셔야할 부분이 있다면..! for문 안에서 클로저를 만들면…
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
위 코드를 실행하면 어떻게 될까요?
답은 3, 3, 3이 출력됩니다.
왜냐면 var는 함수 스코프이기 때문에 i는 하나의 값만 계속 참조하게 돼요. 이럴 땐 let을 쓰거나, IIFE(즉시 실행 함수)를 사용해서 클로저를 잘라줘야 합니다!
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 0, 1, 2
}, 1000);
}
✂️ 자르기 예시 -> IIFE (즉시 실행 함수 표현식)
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => {
console.log(j);
}, 1000);
})(i);
}
0
1
2
설명
- (function(j) { ... })(i)는 i의 현재 값을 j에 복사한 후, 바로 실행됩니다.
- 그 안의 클로저는 더 이상 i가 아닌 j를 기억하므로, 값이 고정됩니다.
- 즉, 반복마다 새로운 j가 만들어지기 때문에 클로저가 잘렸다고 볼 수 있습니다!
✂️ 자르기 -> let 사용 (블록 스코프)
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
0
1
2
설명
- let은 블록 스코프이기 때문에 매 반복마다 새로운 i가 생성됩니다.
- 즉, 반복마다 클로저가 참조하는 i가 다른 메모리 공간에 존재하는 값이 되는 것이죠.
- 그래서 클로저가 각기 다른 i를 기억하게 되어 의도한 대로 작동합니다!
✅ 정리
클로저(Closure) | 함수가 자신이 선언된 외부 범위의 변수를 기억하고 계속 접근할 수 있는 기능 |
주요 사용처 | ✅ 상태 유지 (counter) ✅ 캡슐화 ✅ 비동기 처리 중 값 유지 |
주의할 점 | var로 만든 변수는 클로저에서 모두 같은 값을 참조하게 됨 (ES6 let 사용 추천!) |