Logo

[ES2015] let으로 변수 선언하기 2

이전 포스팅에서 ES2015의 let 키워드에서 대해서 알아보았습니다. 이번 포스팅에서는 for 문에서 let이 어떻게 사용될 수 있는지 살펴보겠습니다.

예제 코드

다음 코드는 사용자 아이디를 받아 가짜 사용자를 담든 후 콜백 함수를 호출하는 비동기 함수입니다. 마치 DB 연동처럼 setTimeout 함수를 이용하여 1초의 지연을 주었습니다.

setTimeout() 함수에 대한 좀 더 자세한 내용은 관련 포스팅을 참고바랍니다.

function findUser(id, cb) {
  setTimeout(function () {
    cb({ id: id, name: "사용자 #" + id });
  }, 1000);
}

그리고 아래 코드는 사용자 아이디의 배열을 받아 여러 사용자의 정보를 출력해주는 함수입니다. findUsers 함수는 배열의 모든 인덱스에 대해서 루프를 돌면서 findUser 함수를 호출하는데요. 첫번째 인자로는 사용자 아이디, 두번째 인자로는 콜백 함수를 넘겨주고 있습니다. 그리고 콜백 함수 내에서는 해당 사용자가 배열의 몇 번째 원소인지와 사용자 정보를 출력하고 있습니다.

function findUsers(ids) {
  for (var i in ids) {
    findUser(ids[i], function (user) {
      console.log(i, "번째 사용자를 출력합니다.");
      console.log(user);
    });
  }
}

코드 실행

그런데 다음과 같이 인자를 넘겨서 위 함수를 실행을 해보면,

findUsers([3, 7, 29, 105]);

다음과 같이 다소 황당한 출력 결과를 얻게 됩니다.

3 번째 사용자를 출력합니다.
{ id: 3, name: '사용자 #3' }
3 번째 사용자를 출력합니다.
{ id: 7, name: '사용자 #7' }
3 번째 사용자를 출력합니다.
{ id: 29, name: '사용자 #29' }
3 번째 사용자를 출력합니다.
{ id: 105, name: '사용자 #105' }

모든 사용자에 대해서 동일한 3 번째 사용자를 출력합니다.가 출력되었습니다. 제가 기대했던 결과는 0 번째 부터 3 번째가 차례로 출력되는 것이었는데 말이죠.

심층 분석

원인을 알아내기 위해 위 코드를 좀 더 심도있게 분석을 해보겠습니다. 일단, ivar를 이용하여 선언하고 있습니다. 이전 포스팅에서 살펴봤던 것 처럼, var로 변수 선언을 할 경우, 해당 변수는 자바스크립트 엔진이 해석할 때 함수 최상단으로 hoisting 해버립니다.

따라서 위 코드는 다음과 같이 해석되어 집니다.

function findUsers(ids) {
  var i;
  for (i in ids) {
    findUser(ids[i], function (user) {
      console.log(i, "번째 사용자를 출력합니다.");
      console.log(user);
    });
  }
}

이렇게 코드를 바꿔놓고 보면 원인이 좀 보이시나요? 동일한 변수 i가 루프를 도는 내내 계속 공유된다는 것을 알 수 있습니다. for 문에서 각 콜백 함수를 넘길 시점에는 i 값이 달랐겠지만, 각 콜백 함수가 실행될 시점에는 for 루프가 이미 끝나서 i가 배열의 마지막 원소의 인덱스로 변경된 이후입니다.

즉, var의 변수 hosting과 콜백 함수의 non blocking 성질이 만나 위와 같은 결과를 만들어냈던 것입니다.

해결 방법

위 문제는 다음과 같이 var 대신에 let을 이용하여 변수를 선언하면 간단하게 해결이 됩니다. let은 변수를 hosting을 하지 않고, block scope을 가지기 때문입니다.

function findUsers(ids) {
  for (let i in ids) {
    findUser(ids[i], function (user) {
      console.log(i, "번째 사용자를 출력합니다.");
      console.log(user);
    });
  }
}

위 코드를 실행해보면, 다음과 같이 우리가 기대했던 결과가 나오게 됩니다.

0 번째 사용자를 출력합니다.
{ id: 3, name: '사용자 #3' }
1 번째 사용자를 출력합니다.
{ id: 7, name: '사용자 #7' }
2 번째 사용자를 출력합니다.
{ id: 29, name: '사용자 #29' }
3 번째 사용자를 출력합니다.
{ id: 105, name: '사용자 #105' }

이번에는 for 루프의 매 패스마다 새롭고 i가 선언되어 0, 1, 2, 3 차례로 할당됩니다. 이렇게 매 패스마다 ilet으로 선언되었기 때문에 서로 독립된 block scope을 가지게 되며, 따라서 각 콜백 함수는 서로 다른 i 값을 인자로 받을 수 있게 되었습니다.

마치면서…

이상으로 for 문에서 var를 사용했을 때 발생하는 문제점과 let을 사용하여 해결하는 방법을 알아보았습니다. 다음 포스팅에서는 ES2015에서 let과 함께 추가된 const 사용법에 대해서 알아보도록 하겠습니다.

참고

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/for…in