[Javascript] for문에서의 비동기 함수 사용 시 스코프와 클로저

유재민·2022년 8월 10일
0

# for문에서의 비동기 함수 사용 시 스코프와 클로저

for문에서 비동기 함수 사용 시 초기 값을 var를 사용하느냐 혹은 let 사용하느냐에 따라 출력 값이 달라질 수 있다. 이러한 결과가 나타나는 이유는 블록 레벨 스코프와 함수 레벨 스코프의 차이 때문이다.


# 사용 예시1 : var

// 예시1 : var 사용
const functionScope = () => {
  for (var i = 0; i < 10; i++) {
    setTimeout(() => {
      console.log(i);
    }, 100);
  }
};

functionScope(); // 10이 10번 반복해서 찍힌다.

functionScope의 결과 값은 10이 10번 찍히게 된다. 이유는 for문 동작 시 setTimeout을 호출하게 되면 콜스택에 함수 컨텍스트를 생성한 후 바로 제거하여 웹API 백그라운드에서 처리된다. 이 후 setTimeout은 비동기적으로 처리되기 때문에 나머지 for문도 방금과 같은 작업을 반복하게 된다. 그렇기 때문에 setTimeout이 타이머에 의해 웹API에서 처리되기 전 for문은 이미 마지막 루프까지 마친 상태이기 때문에 i값은 10이 되고 웹API에서 처리 후 큐를 통해 실행되는 setTimeout 콜백 함수들은 이 i값을 참조하게 되어 모두 10을 반환한다. 또한 i보다 10보다 작은 경우에만 반복을 수행하도록 하였는데 i가 10인 이유는 반복문에서 10이 되는 순간 false가 되어 더 이상 반복문을 실행하지 않지만 i는 이미 10이 되어있기 때문이다.

이와 같은 경우 클로저로 해결할 수 있다.

// 예시2: var 사용 + 클로저
const closerFunction = () => {
  for (var i = 0; i < 10; i++) {
    ((x) => {
      setTimeout(() => {
        console.log(x);
      }, 100);
    })(i);
  }
};

closerFunction(); // 0~9까지 순서대로 찍힌다.

클로저란 간단하게 말하면 내부함수가 외부함수에 접근할 수 있는 것을 말한다. 기존에 동작시키던 방식과 다르게 즉시 실행 함수를 생성한 후 파라미터로 i값을 전달하는 방식을 활용한다. 이런 방식을 활용하게 되면 콜스택에 즉시 실행 함수 컨텍스트가 쌓이고 먼저 사라지더라도 웹API에 콜백으로 넘긴 setTimeout 콜백 함수 스코프에 파라미터로 넘긴 x의 값을 기억하고 있기 때문에 해당 x값을 참조하여 최신 값을 유지할 수 있다.


# 사용 예시2 : let

// 예시3: let 사용
const blockScope = () => {
  for (let i = 0; i < 10; i++) {
    setTimeout(() => {
      console.log(i);
    }, 100);
  }
};

blockScope(); // 0~9까지 순서대로 찍힌다.

예를 들어 functionScope()와 같이 var를 사용한 경우 var는 함수 레벨 스코프를 가지기 때문에 for 루프마다 새로운 스코프를 만들어내는 것이 아닌 functionScope 내부에 하나의 스코프로만 존재하게 된다. 그렇기 때문에 for 루프마다 동일한 참조를 바인딩해서 사용하게 되는 것이다. 반면 blockScope()와 같이 let을 사용한 경우 let은 블록 레벨 스코프를 가지기 때문에 for 루프마다 새로운 스코프를 만들어낸다. i가 0인 스코프, 1인 스코프, 2인 스코프 ... 등등으로 만들어내게 되고 이 각 루프마다 만들어지는 새로운 스코프를 참조하게 되므로 서로 다른 참조를 하게 되어 동일한 값이 출력되지 않게 된다.


profile
프론트엔드 개발자

0개의 댓글