클로저는 함수와 함수가 선언된 어휘적 환경의 조합을 의미한다!
하지만, 클로저는
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
만약 다음과 같이 반복문 안에서, 1초 간격으로 i를 출력하려 했을때 결과는 어떻게 될까?
0부터 시작해 반복문을 돌게 되면서, 1초 간격으로 console 출력값으로 0, 1, 2, 3, 4 를 차례대로 출력하는 것을 예상하겠지만,
결과적으로는 그렇지 않는다.
다음과 같이, 1초마다 숫자 5가 찍혀서 총 다섯번의 5가 출력되는 것을 알 수 있다.
반복문의 흐름을 생각해본다면 아래와 같이 그 과정을 나눠볼 수 있는데,
0 | setTimeout(() => console.log(i), 0 * 1000); → 즉시 실행 |
1 | setTimeout(() => console.log(i), 1 * 1000); → 1초 후 실행 |
2 | setTimeout(() => console.log(i), 2 * 1000); → 2초 후 실행 |
3 | setTimeout(() => console.log(i), 3 * 1000); → 3초 후 실행 |
4 | setTimeout(() => console.log(i), 4 * 1000); → 4초 후 실행 |
이때, setTime 실행시 콜백함수가 바로 실행되는 것이 아니라 예약할 뿐이며,
시간이 흐를때까지 for 루프는 엄청나게 빠르게 실행되고 끝나게 된다.
따라서, var 로 선언된 변수는 함수 스코프를 갖기 때문에 for 루프가 끝날 때 i = 5 가 되어버린다.
해서 모든 setTimeout이 실행될 때, 클로저가 현재의 스코프를 기억하기 때문에 i는 이미 5가 되어 있어 1초마다 5가 출력되는 것처럼 보이는 것이다.
예상치 못한 결과를 피하기 위해서는 블록 레벨 스코프를 갖는 let를 사용해 i를 선언하거나,
즉시 실행 함수를 통해서 고유하게 변수를 관리할 수 있도록 하는 방법이 있다.
1️⃣ 블록 레벨 스코프를 갖는 let 사용
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
let은 각 반복(블록)에서 새로운 변수 i를 생성하기 때문에, setTimeout 실행시 i 값이 유지된다.
2️⃣ 즉시 실행 함수(IIFE)를 사용하게 될 경우
for (var i = 0; i < 5; i++) {
setTimeout(
(function (sec) {
return function () {
console.log(sec);
};
})(i),
i * 1000
);
}
즉시 실행 함수 (function (sec) { ... })(i)가 호출되면서, 클로저 사용하여 현재 i 값을 sec에 복사해서 저장하기에 마찬가지로 위의 결과와 동일하게 작동한다.
'개발 > 💡 TIL' 카테고리의 다른 글
[React] 리액트의 단방향성과 JSX (1) | 2025.03.06 |
---|---|
[React] Link는 어떻게 작동할까? (0) | 2025.02.19 |