왜 다르게 동작할까요?

About JS, 스코프, 클로저
2023/11/09

❗️ 스코프와 클로저의 개념에 대해 자세히 다루지 않습니다.

함수 a()b() 모두 콜백함수에 클로저가 사용됩니다. 하지만 결과가 다른 이유는 두 변수의 스코프가 다르기 때문입니다.

var는 함수 스코프, let은 블록 스코프

변수의 선언 방식에 따라 스코프가 달라집니다.

var함수 레벨의 스코프를 가지기 때문에 for문이 종료되어도 함수 a의 스코프 내에서 참조가 가능합니다. 아래처럼요.

function example() {
  for (var i = 0; i < 10; i++) {
    // do something
  }
  console.log(i); // 10
}

하지만 let블록 레벨 스코프를 가지기 때문에 for문이 종료되면 해당 블록 내에서 선언한 변수는 접근할 수 없습니다.

function example() {
  for (let i = 0; i < 10; i++) {
    // do something
  }
  console.log(i); // Uncaught ReferenceError: i is not defined
}

그럼 for문도 함수 실행도 종료된 이후 setTimeout내 콜백에서 i를 참조할 수 있는 이유는 무엇일까요?

클로저

콜백 함수가 외부 스코프의 참조를 가지고 있어 클로저가 형성되어 a(), b()의 실행이 완료된 후에도 해당 값을 저장하고 참조를 유지할 수 있기 때문입니다.

let

let 키워드는 각 for 반복문의 블록마다(반복마다) 변수 i에 대해 독립된 블록 스코프를 생성합니다. 따라서 콜백 함수는 고유한 i 값에 대한 참조를 유지하는 클로저를 생성하게 됩니다.

코드 상으로는 변수 선언이 한번이지만 블록 스코프를 가지는 let의 특성상 매 순회마다 새로 선언, 할당이 발생하는 것처럼 동작합니다. (* 실제로는 각 반복마다 변수가 "새로 선언"되는 것이 아니라, 단일 선언이 블록 스코프 내에서 각기 다른 인스턴스로 처리됨)

결과적으로 0~9 까지 순회하며 10개의 독립적인 변수 i를 기억하며, 각각 다른 값(0~9)이 출력됩니다.

var

var의 경우 함수 레벨 스코프를 가지기 때문에 함수 b 내부가 변수 i의 스코프가 됩니다. 따라서 순회문 내에서도 동일한 i를 참조하며, 콜백함수 또한 마찬가지로 동일한 변수 i를 참조하게 됩니다._createMdxContent 10개의 콜백함수가 모두 동일한 i를 참조하기 때문에 콜백함수 실행 시점의 i 값인 10이 10번 출력되게 됩니다.

위와 같이 변수 선언 방식은 코드의 동작 방식에 영향을 줍니다. 불필요한 오류를 방지하기 위해선 팀 내 변수 선언 방식을 한 가지로 통일하는 것이 좋겠죠?

스코프 외에도 재선언 방지, TDZ 생성 등 잠재적인 오류를 막기 위해 개선된 변수 선언 방식인 const, let을 사용하는 것이 모던 자바스크립트에선 권장됩니다.