기술 면접 관련 구글링을 하다가 우연히 for문에서 var을 사용할 때와 let 을 사용할 때 결과가 다르다는 것을 알게 되었다.
이 참에 var와 let의 차이와 스코프의 개념을 한번 짚고 넘어가야겠다는 생각이 들었다.
1. var와 let의 차이
var와 let으로 선언된 변수는 각각 영향을 미칠 수 있는 유효범위를 가지는데, var는 function scope, let은 block scope를 가진다.
function scope는 우리가 아는 흔한 함수를 생각하면 되고, function scope를 유효범위로 가지는 변수는 함수 안에서 선언되면 함수 밖에서 접근하지 못한다.
아래의 코드를 보면 i가 var로 선언되었으므로 전역 변수가 되어 익명 함수 실행 시 i값은 무조건 5가 출력되는 것이다.
const array = [];
for (var i = 0; i < 5;i++){
array.push(function(){
console.log(i);
});
array[i](); // 0 1 2 3 4
}
// for문이 완료된 후 참조한 전역변수 i 값이 5이기 때문에 모든 console.log(i) 값이 5만 출력되는 것이다.
array[0](); // 5
array[1](); // 5
array[2](); // 5
array[3](); // 5
array[4](); // 5
참고로 전역 변수를 만드는 방법은 다음과 같다.
- 선언하지 않고 직접 사용
- var의 경우 함수 밖에서 선언
- let의 경우 블럭 밖에서 선언
https://offbyone.tistory.com/447
반면에 let의 경우 for문이 반복될 때마다 새로운 i 값으로 모든 내부 블록을 돈다. for문에서 let을 사용할 경우 for 문 내부 블록도 반복되며 코드 블록을 위핞 새로운 렉시컬 환경이 생성된다.
const array = [];
for (let i = 0; i < 5;i++){
array.push(function(){
console.log(i);
});
}
array[0](); // 0
array[1](); // 1
array[2](); // 2
array[3](); // 3
array[4](); // 4
block scope는 if나 for 문 등에서 사용되는 {} 대괄호이며, 마찬가지로 블록 안에서 선언되면 블록 밖에서 접근하지 못한다.
var는 function scope를 따르기 때문에 블록 안에서 선언된 변수가 블록 밖에서도 접근할 수 있는 것이다.
for (var i = 0;i < 5;i++){
console.log(i)
}
console.log(i) // 5
for (let i = 0;i < 5;i++){
console.log(i)
}
console.log(i) // i is not defined
2. for문 안에서 var와 let을 사용할 경우의 차이(feat. setTimeout)
먼저 setTimeout 함수의 구문을 간단히 살펴보자
setTimeOut(callback function, time)
콜백 함수는 지정한 시간이 되면 실행되는 함수이다. time에는 콜백 함수를 실행시킬 타이밍이다. 예를 들어 time이 1000이면 1초 뒤 콜백 함수를 실행시키라는 의미이다.
for문에서 var를 사용할 경우를 살펴보자.
useEffect(() => {
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 1000 * (i+1));
}
}, []);
// 10
// 10
// 10
// 10
// 10
...
위와 같은 코드에서는 매 초마다 0,1,2,3 .. 식으로 1이 늘어난 값이 아닌 10이 출력된다. 그 이유는 1~5 초 뒤 매 초마다 콜백함수를 실행할 때 i를 참조하는 데 그때 i의 값이 10이기 때문이다.
과정을 간단히 살펴보자.
i = 0 , 콜백함수 0 대기 중, 1초 뒤로 지정됨
i = 1 , 콜백함수 1 대기 중, 2초 뒤로 지정됨
i = 2 , 콜백함수 2 대기 중, 3초 뒤로 지정됨
i = 3 , 콜백함수 3 대기 중, 4초 뒤로 지정됨
i = 4 , 콜백함수 4 대기 중, 5초 뒤로 지정됨
i = 5 가 되어 반복문 종료
(위 작업이 1초 전에 끝남)
1초 뒤, 콜백함수 0 실행하며 i 참조 => 5
2초 뒤, 콜백함수 1 실행하며 i 참조 => 5
3초 뒤, 콜백함수 2 실행하며 i 참조 => 5
4초 뒤, 콜백함수 3 실행하며 i 참조 => 5
5초 뒤, 콜백함수 4 실행하며 i 참조 => 5
반면에 let을 사용할 경우, 반복문이 한 번 돌 때마다 i 변수를 참조하여 모든 내부 블록이 같이 돌면서 값을 수집한다. 그 수집한 값을 지정한 시간에 출력하여 우리가 의도한 대로 매 초마다 1씩 증가된 값이 출력되는 것을 볼 수 있다.
useEffect(() => {
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i);
}, 1000 * (i+1));
}
}, []);
// 0
// 1
// 2
// 3
// 4
...
결론적으로 for문을 사용할 때, 우리가 의도한 값을 얻기 원한다면 블록 스코프인 let을 사용하자.
참고한 블로그)
https://fromnowwon.tistory.com/entry/for%EB%AC%B8-let
'CS > Javascript' 카테고리의 다른 글
자바스크립트 렉시컬 환경이란? (0) | 2023.03.28 |
---|---|
isNaN() 반환 값?! (0) | 2023.03.15 |
[js] slice vs splice / substr vs substring (0) | 2023.02.15 |
[js] for in & for of / includes & in (0) | 2022.10.07 |
[js] for Each & Map (0) | 2022.09.08 |