[JS] setTimeout 문제

in #js6 years ago

JavaScript에는 sleep() 과 같이 다른 언어에서 지원하는 현재 쓰레드를 잠깐 멈추는 함수/메소드가 없고, setTimeout() 이라는 독특한 함수가 있는데 아래와 같은 경우에 사용하면 된다.

"0부터 9까지 값들을 3초 뒤에 출력하고 싶다."

그럼 코드로 한번 짜보자.

for (var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 3000);
}

실행을 해보면 다음과 같다.

음... 우리가 원하는건 0부터 9까지 출력인데 10이 10번 찍혔다.

왜?

바로 JavaScript의 독특한 변수 스코프 때문이다.
일반적인 다른 프로그래밍 언어들의 변수 스코프는 블록 {} 안에서 유효하다.
하지만 JavaScript의 var 키워드로 선언된 변수는 블록이 아닌 함수 스코프를 갖게 된다.
따라서 for 문에서 var i = 0 으로 선언된 i는 전역변수로 선언되고
setTimeout() 내부에서 사용되는 i 역시 전역 변수를 참조한다.

즉, for 구문을 통해 10번의 반복이 끝난 뒤 setTimeout()이 첫 번째 파라미터로 지정된 callback 함수를 실행하게 되는데 이때는 이미 i의 값이 10으로 증가된 상태이므로 10번 모두 10이 출력되는 것이다.

그럼 이를 해결할 수 있는 방법은 뭐가 있을까? 두 가지 방법을 살펴보자.

1. 즉시 실행 함수


i의 값을 즉시 실행 함수로 전달해서 내부 스코프의 지역변수로 바꿔주자!

for (var i = 0; i < 10; i++) {
    (function(index) {
        setTimeout(function() {
            console.log(index);
        }, 3000);
    })(i);
}

결과는 아래와 같이 이쁘게 0부터 9까지 출력된다.

2. Closure


이 부분이 JavaScript의 꽃 중 하나가 아닐까 생각된다.
closure를 사용하면 외부 함수가 종료되어도 내부 함수가 외부 함수의 변수들에 접근할 수 있다. 또한 1번 방법과 마찬가지로 i의 값을 callback 함수의 지역변수로 전달하여 closure에서 참조하므로 제대로 된 결과를 만들어 낼 수 있다!

for (var i = 0; i < 10; i++) {
    setTimeout(function(index) {
        return function() {
            console.log(index);
        };
    }(i), 3000);
}

결과는 다음과 같다.

정리


JavaScript에서는 함수 스코프를 사용하므로 다른 프로그래밍 언어에 익숙한 사람들은 많은 혼란을 느낄 것이다.
하지만 이런 스코프와 컨택스트(나중에 따로 포스팅)를 잘 이해하면 다양하고 기발한 방법들로 코드를 작성할 수 있다.
그래서 JavaScript가 너무 좋다.

PS. var i 가 아닌 let i로 변수를 선언하면 i는 함수 스코프가 아닌 블록 스코프로 선언된다. 이는 es6 문법이므로 알아두면 참 좋고 편하다.

Sort:  

좋은 글로 자주 뵈어요~^^

네 감사합니다 ^^ 앞으로 좋은 글 자주 올릴테니 많이 놀러오세요~