Notice
Recent Comments
Link
«   2020/04   »
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    
Tags
more
Archives
Today
0
Total
843
관리 메뉴

My Blog

[JavaScript] for loop에서 let의 동작 본문

프로그래밍

[JavaScript] for loop에서 let의 동작

Senior9324 2019. 2. 9. 21:49
for (var i = 0; i < 5; i++) {
  setTimeout(function () { console.log(i); }, 100 * i);
}

위 코드의 출력결과가 5개의 5라는 사실은 지금 와서는 정말 당연한 동작으로 보인다.


또한 위 코드를 let으로 바꾸면 '생각한 대로의 동작'이 나온다는 사실도 알고 있다.


그런데 let을 쓰면 어떻게 해서 이 '생각한 대로의 동작'이 나올까?


보통 이것을 let이 block 단위의 유효 범위(block scope)를 갖기 때문이라고 설명한다.


하지만 이 설명은 실제로는 아무것도 설명해주지 않는다.

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

위 코드는 처음의 var 예시와 같이 5가 5개 나온다.


또, for loop에 let을 쓸 때도 다음과 같이 i 변수를 변경할 수 있다.

for (let i = 0; i < 5; i++) {
  setTimeout(function () { console.log(i); }, 100 * i);
  i++;
}
// > 1
// > 3
// > 5

그러니까 for 문의 블록 안에서 i를 변경하면 반복 프로세스에 영향은 가지만 블록 안의 비동기 함수에서 i를 접근할 때에는 해당 루프에서의 마지막 값이 반환되는 것이다.


이를 설명하기 위해서 ECMAScript의 사양서를 들춰보도록 하자.


  1. If perIterationBindings has any elements, then
    1. Let lastIterationEnv be the running execution context’s LexicalEnvironment.
    2. Let outer be lastIterationEnv’s outer environment reference.
    3. Assert: outer is not null.
    4. Let thisIterationEnv be NewDeclarativeEnvironment(outer).
    5. For each element bn of perIterationBindings do,
      1. Let status be thisIterationEnv.CreateMutableBinding(bn, false).
      2. Assert: status is never an abrupt completion.
      3. Let lastValue be lastIterationEnv.GetBindingValue(bn, true).
      4. ReturnIfAbrupt(lastValue).
      5. Perform thisIterationEnv.InitializeBinding(bn, lastValue).
    6. Set the running execution context’s LexicalEnvironment to thisIterationEnv.
  2. Return undefined

ECMAScript 2015 Language Specification – ECMA-262 6th Edition § 13.7.4.9 CreatePerIterationEnvironment
© 2015 Ecma International

1-a에서 이전 루프의 LexicalEnvironment를 lastIterationEnv로 정의하고, 1-d에서 새로운 LexicalEnvironment인 thisIterationEnv를 생성한다. perIterationBindings, 즉 for문 괄호 내에서 정의된 let 변수들에 대해서, lastIterationEnv의 값을 받아와 thisIterationEnv에 바인딩한다. 1-f에서 thisIterationEnv를 실행 문맥으로 설정한다.


이렇게, let 변수를 for문에 사용하면 특별한 동작을 통해 프로그래머들에게 직관성을 제공한다.


let의 동작은 직관적이지만 이 글은 직관적이지 않은듯 하다. 마지막으로 JavaScript의 Transpiler인 Babel이 이를 변환하는 방법을 알아보자.

// Original Code
for (let i = 0; i < 6; i++) {
  setTimeout(function () { console.log(i) }, 100 * i)
  i++
}

// Babel-transpiled Code
var _loop = function _loop(_i) {
  setTimeout(function () {
    console.log(_i);
  }, 100 * _i);
  _i++;
  i = _i;
};
for (var i = 0; i < 6; i++) {
  _loop(i);
}
0 Comments
댓글쓰기 폼