상세 컨텐츠

본문 제목

[JavaScript] let과 var의 스코프, 클로저

카테고리 없음

by esoesmio 2024. 5. 14. 21:59

본문

function a(){

    for(var i = 0; i < 5; i++){
        setTimeout(
            ()=>{
                console.log(i);
            },i*1000

        )
    }

}

a();

 

 

이거 왜 55555 나올까?

 

이 코드에서 모두 5가 출력되는 이유는 JavaScript의 클로저(closure)와 `var` 키워드의 스코프 특성 때문입니다. 여기서 사용된 `var` 키워드는 함수 레벨 스코프를 갖습니다. 이것은 `for` 루프 안에서 선언된 변수 `i`가 루프 밖에서도 같은 환경을 공유한다는 것을 의미합니다.

### 자세한 설명:
1. `for` 루프는 `i`를 0부터 시작하여 4까지 증가시키면서 각각의 반복에서 `setTimeout`을 설정합니다.
2. 각 `setTimeout` 함수는 비동기적으로 실행되며, 각각의 콜백 함수가 실행될 때 `i`의 현재 값을 참조합니다.
3. `setTimeout`의 콜백 함수는 `i`가 증가된 후에 실행됩니다. 모든 콜백 함수들은 `i`의 최종 값을 참조하게 되는데, 이는 루프가 완료된 후 `i`의 값이 5이므로 모든 콜백에서 5를 출력하게 됩니다.

### 해결 방법:
이 문제를 해결하기 위해 각각의 `setTimeout` 콜백 함수가 그것이 생성될 때의 `i` 값을 "기억하게" 할 수 있습니다. 이를 위해 몇 가지 방법을 사용할 수 있습니다.

**방법 1: `let` 키워드 사용**
- `let` 키워드는 블록 스코프를 갖습니다. `for` 루프에서 `let`을 사용하면 각 반복마다 새로운 `i`가 생성됩니다. 따라서 각 `setTimeout` 콜백은 해당 반복에서의 `i` 값을 "캡처"할 수 있습니다.
```javascript

function a() {
    for(let i = 0; i < 5; i++) {
        setTimeout(() => {
            console.log(i);
        }, i * 1000);
    }
}
a();


```

**방법 2: 클로저 사용**
- 즉시 실행 함수 표현식(IIFE)을 사용하여 각 반복마다 `i` 값을 캡처할 수 있습니다.
```javascript

function a() {
    for(var i = 0; i < 5; i++) {
        (function(j) {
            setTimeout(() => {
                console.log(j);
            }, j * 1000);
        })(i);
    }
}
a();


```
이러한 방법들은 각각의 `setTimeout` 콜백에 그 순간의 `i` 값을 고정시켜 줌으로써, 의도한 대로 0, 1, 2, 3, 4를 순차적으로 출력하도록 합니다.

 

 

 

 

 

JavaScript에서 `var` 키워드로 선언된 변수는 몇 가지 특징을 가지고 있습니다. 이러한 특성이 왜 `for` 루프 내의 `setTimeout`에서 모두 5가 출력되는지에 대한 원인을 설명하는 데 중요합니다. 여기에는 변수의 스코핑 규칙, 호이스팅 특성 및 클로저의 개념이 관련되어 있습니다.

### 1. **변수 스코핑과 호이스팅**
`var`로 선언된 변수는 함수 스코프를 가집니다. 함수 스코프란 변수가 해당 함수 내에서 전역적으로 접근 가능하다는 것을 의미합니다. 또한, `var`는 호이스팅되어 함수의 최상위로 끌어올려집니다(선언만, 초기화는 호이스팅 되지 않음). 이는 코드의 어느 위치에서 변수를 선언하든, 그 함수의 시작 부분에서 선언된 것으로 처리된다는 뜻입니다.

### 2. **`for` 루프와 `var`**
`var`로 선언된 `i`는 `for` 루프의 전체에서 단 하나의 변수로 존재합니다. 따라서 루프의 각 반복에서 `i`를 수정할 때마다 같은 변수 `i`의 값이 변경되고, 루프가 종료된 후에는 `i`의 값이 마지막으로 설정된 값(루프 조건이 거짓이 되는 시점의 값)으로 남아 있습니다. 이 경우에는 `i`가 5가 되는 순간 `for` 루프가 종료됩니다.

### 3. **`setTimeout`과 클로저**
`setTimeout` 함수 내부에서 사용된 익명 함수(콜백 함수)는 주변 환경(클로저)에 대한 참조를 유지합니다. 여기서 중요한 것은 이 클로저가 `var`로 선언된 단일 `i` 변수를 참조한다는 점입니다. `setTimeout`의 각 콜백 함수가 실행될 때 `i`의 현재 값을 참조하게 되는데, `for` 루프가 이미 종료된 후이므로 `i`의 값은 5입니다. 그 결과, 각 콜백은 모두 5를 출력하게 됩니다.

### 구체적인 예시 설명
```javascript

function a(){
    for(var i = 0; i < 5; i++){
        setTimeout(
            ()=>{
                console.log(i);
            },i*1000
        );
    }
}
a();


```
이 코드에서는 다음과 같은 일련의 사건이 발생합니다:

- `var i`는 함수 `a`의 시작부에서 선언됩니다(호이스팅).
- `i`는 0부터 4까지 증가하면서 `setTimeout`을 등록합니다.
- 각 `setTimeout`은 함수 `a`의 실행 컨텍스트 안에 있는 단일 `i`를 참조합니다.
- `for` 루프가 종료될 때 `i`는 5가 되고, 이 때 모든 `setTimeout` 콜백이 이 값을 참조합니다.
- 각각의 `setTimeout` 콜백이 실행될 때(`i*1000` 밀리초 후), `console.log(i)`는 `i`의 최종 값을 출력하므로 5가 출력됩니다.

이 설명에서는 JavaScript의 `var` 키워드의 동작 방식과 비동기 함수의 클로저 사용이 결합되어 예상치 못한 결과를 초래하는 과정을 자세히 설명하였습니다. 이해를 돕기 위해 `let`을 사용하거나 IIFE(

즉시 실행 함수 표현식)를 사용하는 등의 방법으로 이 문제를 해결할 수 있습니다.

댓글 영역