자바스크립트 퀴즈북 리마인드 Day 7: 클로저 버그와 오래된.
클로저가 값 복사가 아니라 렉시컬 환경 참조라는 점을 stale closure, 루프 콜백, React 이벤트 리뷰 관점에서 정리합니다.
오늘의 질문: “클로저는 값을 복사해 둔 스냅샷일까, 변수 바인딩을 계속 따라가는 참조일까?”
function makeCounters() {
const result = [];
for (var i = 0; i < 3; i += 1) {
result.push(function read() {
return i;
});
}
return result;
}
const [a, b, c] = makeCounters();
console.log(a(), b(), c());출력은 3 3 3입니다. 세 함수가 각각 0, 1, 2를 복사해 둔 게 아니라, var i라는 같은 함수 스코프 바인딩을 닫아두었기 때문입니다. 루프가 끝난 뒤 그 바인딩의 값은 3입니다.
let으로 바꾸면 결과가 달라집니다.
for (let i = 0; i < 3; i += 1) {
result.push(() => i);
}
// 0, 1, 2let은 반복마다 새 블록 바인딩을 만들기 때문에 각 콜백이 닫아두는 대상이 달라집니다.
“함수가 만들어질 때 값이 복사된다”라고 생각하면 클로저 버그를 잘못 고칩니다. 클로저가 기억하는 것은 보통 값 자체가 아니라 렉시컬 환경에 있는 바인딩입니다. 그래서 같은 바인딩을 여러 함수가 공유하면 나중 값이 같이 보이고, 렌더·이벤트·타이머 사이에 생성 시점이 어긋나면 오래된 경로를 읽습니다.
React에서 자주 보일 뿐, 원인은 자바스크립트 함수 생성 위치입니다.
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
console.log(count);
}, 1000);
return () => clearInterval(id);
}, []);
}의존성 배열이 비어 있으면 interval 콜백은 처음 렌더의 count 바인딩을 닫아둡니다. 화면에서는 값이 바뀌어도 이 콜백은 계속 첫 렌더의 경로를 볼 수 있습니다.
코드 리뷰에서는 “왜 count가 최신이 아니지?”보다 먼저 “이 함수가 어느 렌더에서 만들어졌지?”를 묻는 편이 낫습니다.
| 상황 | 의심할 부분 | 흔한 해결 방향 |
|---|---|---|
| 루프 안 콜백이 모두 같은 값 | 같은 var 바인딩 공유 | let, 별도 함수, 값 인자 전달 |
| 타이머가 오래된 상태 출력 | 콜백 생성 시점과 상태 변경 시점 분리 | 의존성 배열 보정, ref, updater 함수 |
| 이벤트 핸들러가 과거 props 사용 | 핸들러가 오래된 렌더에서 생성 | 최신 값을 인자로 넘기거나 핸들러 갱신 |
| async 후 상태 업데이트 꼬임 | 완료 시점이 생성 시점보다 늦음 | 취소 플래그, AbortController, functional update |
핵심은 “클로저를 없앤다”가 아닙니다. 클로저는 유용합니다. 다만 어떤 바인딩을 닫아두는지 코드가 말해주지 않으면 나중 실행에서 버그가 됩니다.
콜백이 만들어지는 위치와 실행되는 위치가 멀리 떨어져 있지 않은가?
반복문 안 콜백이 같은 바인딩을 공유하지 않는가?
useEffect, useCallback, 이벤트 리스너의 의존성이 실제로 읽는 값과 맞는가?
최신 값이 필요하다면 state updater, ref, 인자 전달 중 어떤 모델이 더 명확한가?
오래된 async 결과가 최신 화면 상태를 덮어쓸 가능성은 없는가?
클로저를 값 저장소로 외우면 “왜 예전 값이지?”에서 멈춥니다. 바인딩 참조로 보면 생성 위치, 공유 여부, 실행 지연을 순서대로 추적할 수 있습니다.
Post Q&A
자바스크립트 퀴즈북 리마인드 Day 7: 클로저 버그와 오래된 값 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!
스코프 체인, 클로저, var/let/const 호이스팅 차이를 콜백·상태 버그 리뷰 관점에서 짧게 정리합니다.