이 글은 Dev quiz 어플리케이션 null의 퀴즈 해설을 위한 글입니다.
보러가기 -> https://github.com/0hhanum/null_ios
question:
- 다르게 동작하는 이유는?
========================================
function a() {
for (let i = 0; i < 10; i++) {
const func = () => console.log(i);
setTimeout(func, 100);
}
}
a();
========================================
function b() {
for (var i = 0; i < 10; i++) {
const func = () => console.log(i);
setTimeout(func, 100);
}
}
b();
========================================
choices:
- 클로저 때문
- 스코프 때문
answer:
- B
❗️ 스코프와 클로저의 개념에 대해 자세히 다루지 않습니다.
함수 a()
와 b()
모두 콜백함수에 클로저가 사용됩니다.
하지만 결과가 다른 이유는 두 변수의 스코프가 다르기 때문입니다.
변수의 선언 방식에 따라 스코프가 달라집니다.
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
키워드는 각 for
반복문의 블록마다(반복마다) 변수 i
에 대해 독립된 블록 스코프를 생성합니다.
따라서 콜백 함수는 고유한 i
값에 대한 참조를 유지하는 클로저를 생성하게 됩니다.
코드 상으로는 변수 선언이 한번이지만 블록 스코프를 가지는 let
의 특성상 매 순회마다 새로 선언, 할당이 발생하는 것처럼 동작합니다.
(* 실제로는 각 반복마다 변수가 "새로 선언"되는 것이 아니라, 단일 선언이 블록 스코프 내에서 각기 다른 인스턴스로 처리됨)
결과적으로 0~9 까지 순회하며 10개의 독립적인 변수 i를 기억하며, 각각 다른 값(0~9)이 출력됩니다.
var
의 경우 함수 레벨 스코프를 가지기 때문에 함수 b
내부가 변수 i
의 스코프가 됩니다.
따라서 순회문 내에서도 동일한 i
를 참조하며, 콜백함수 또한 마찬가지로 동일한 변수 i
를 참조하게 됩니다._createMdxContent
10개의 콜백함수가 모두 동일한 i
를 참조하기 때문에 콜백함수 실행 시점의 i
값인 10이 10번 출력되게 됩니다.
위와 같이 변수 선언 방식은 코드의 동작 방식에 영향을 줍니다.
불필요한 오류를 방지하기 위해선 팀 내 변수 선언 방식을 한 가지로 통일하는 것이 좋겠죠?
스코프 외에도 재선언 방지, TDZ 생성 등 잠재적인 오류를 막기 위해 개선된 변수 선언 방식인
const
, let
을 사용하는 것이 모던 자바스크립트에선 권장됩니다.