let vs var 함정 완전 정리 — 스코프·호이스팅·반복문 클로저
자바스크립트에서 변수를 선언하는 var와 let은 비슷해 보이지만, 동작이 꽤 다르다. 그리고 그 차이를 모르면 "분명 코드는 맞는데 값이 이상하게 나오는" 버그를 만난다. 대표적인 게 반복문 안에서 함수를 만들 때다. for (var i...)로 돌린 코드는 나중에 실행되는 함수들이 전부 마지막 값(예: 3, 3, 3)을 찍고, let으로 바꾸면 0, 1, 2가 제대로 나온다. 같은 로직인데 var냐 let이냐로 결과가 갈리는 것이다. 이 차이의 뿌리는 스코프에 있다. var는 함수 단위, let은 블록 단위로 변수의 유효 범위가 다르기 때문이다. 이 글은 var와 let의 근본 차이, var가 만드는 대표 함정, 그리고 왜 const를 기본으로 쓰고 var를 지양하는지를 정리한다.

이 글의 구성
🔎 var와 let의 근본 차이 — 스코프
var와 let의 모든 차이는 스코프(유효 범위)에서 갈린다. var는 함수 스코프다. 함수 안 어디서 선언하든 그 함수 전체에서 유효하고, if나 for의 블록 {}은 var의 범위를 나누지 못한다. 반면 let은 블록 스코프다. 자신이 선언된 블록 {} 안에서만 유효하고, 블록을 벗어나면 사라진다.
이 차이가 실제로 어떻게 드러나는지 보면 명확하다. if 블록 안에서 var로 선언한 변수는 블록 밖에서도 접근되지만, let으로 선언하면 블록 밖에서는 "정의되지 않았다"며 막힌다. 블록마다 변수를 가둘 수 있느냐 없느냐가 var와 let을 가르는 핵심이고, 뒤에 나올 반복문 클로저 함정도 결국 이 스코프 차이에서 비롯된다.
핵심 — 함수 스코프냐 블록 스코프냐
var는 함수 단위, let은 블록 단위로 유효 범위가 정해진다. var는 if·for 블록을 뚫고 함수 전체에서 살아 있어 의도치 않게 유출되거나 덮어써지기 쉽다. let은 블록에 갇혀 예측 가능하다. 이 스코프 차이가 호이스팅·반복문 클로저까지 이어진다.
⚖️ var vs let 4가지 차이
스코프에서 출발한 차이는 네 가지로 정리된다. 이 표만 잡으면 대부분 설명된다.
| 구분 | var | let |
|---|---|---|
| 스코프 | 함수 스코프 | 블록 스코프 |
| 호이스팅 | undefined로 초기화(선언 전 접근 가능) | TDZ(선언 전 접근 시 에러) |
| 재선언 | 같은 스코프에서 가능(덮어씀) | 불가(SyntaxError) |
| 전역 선언 시 | 전역 객체 속성 생성(window.x) | 전역 객체 속성 안 만듦 |
정리하면 이렇다. 스코프는 var가 함수·let이 블록이고, 호이스팅은 var가 undefined로 채워져 선언 전에 써도 에러가 없지만 let은 초기화 전 구간(TDZ)이라 선언 전 접근이 에러다. 재선언은 var가 같은 스코프에서 또 선언해도 조용히 덮어쓰지만 let은 문법 에러로 막는다. 마지막으로 전역에서 선언하면 var는 window 같은 전역 객체의 속성이 되지만 let은 그렇지 않다(단 이건 일반 스크립트 기준이며, ES 모듈이나 Node에서는 최상위도 모듈 스코프라 var여도 전역 객체 속성을 만들지 않는다). 하나같이 let이 더 안전하고 예측 가능한 쪽이다.
🧩 var가 만드는 대표 함정 4가지
함정 1 — 반복문 클로저
가장 유명하다. for (var i...) 안에서 만든 함수들이 나중에 실행되면 모두 마지막 i 값을 찍는다. var는 함수 스코프라 i가 하나뿐이고, 모든 함수가 그 하나를 공유하기 때문이다. let은 반복마다 새 i를 만들어 각 함수가 자기 값을 기억한다.
함정 2 — 블록 밖으로 유출
if나 for 블록 안에서 var로 선언한 변수가 블록 밖에서도 살아 있어, 의도치 않게 접근되거나 값이 바뀐다. 잠깐 쓰려던 임시 변수가 함수 전체를 오염시킨다. let은 블록에 갇혀 이런 유출이 없다.
함정 3 — 선언 전 접근이 undefined
var는 선언 전에 접근해도 에러 없이 undefined가 나온다. 오타나 순서 실수가 에러로 드러나지 않고 조용히 undefined로 넘어가, 버그를 늦게 발견하게 만든다. let은 선언 전 접근을 TDZ 에러로 즉시 알려 준다.
함정 4 — 실수로 재선언·덮어쓰기
var는 같은 이름을 다시 선언해도 조용히 덮어쓴다. 큰 함수에서 같은 이름을 무심코 또 var로 선언하면 앞의 값이 사라지는데 아무 경고가 없다. let은 같은 스코프 재선언을 문법 에러로 막아 실수를 잡아 준다.
💻 반복문 클로저 재현과 해결
var 함정의 대표인 반복문 클로저를 직접 보자. setTimeout으로 나중에 실행되는 함수들이 어떤 값을 찍는지가 var와 let에서 갈린다.
// var — 모든 함수가 하나의 i 공유
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// ✗ 3, 3, 3 (반복 끝난 뒤 i는 3)
// let — 반복마다 새 i 바인딩
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// ✓ 0, 1, 2
원리는 이렇다. var는 함수 스코프라 반복문 전체에 i가 하나뿐이다. setTimeout 콜백들은 그 하나의 i를 참조하는데, 콜백이 실행될 때는 이미 반복이 끝나 i가 3이 되어 있다. 그래서 전부 3을 찍는다. let은 반복이 돌 때마다 그 회차의 i를 새로 만들어(블록 스코프), 각 콜백이 자기 회차의 값을 기억한다. 그래서 0, 1, 2가 나온다. 해결은 간단하다. 반복문 카운터를 let으로 쓰면 된다.
// (참고) 옛날엔 var로 하려면 IIFE로 값을 가뒀다
for (var i = 0; i < 3; i++) {
((n) => setTimeout(() => console.log(n), 100))(i);
}
// 지금은 let 한 줄이면 끝
🛠 권장 — const 기본, var 지양
현대 자바스크립트의 권장 습관은 분명하다. 기본은 const, 재할당이 필요할 때만 let, var는 특별한 이유가 없으면 쓰지 않는다. 이 순서가 실수를 가장 많이 막아 준다.
변수 선언 원칙
한마디로, let과 const는 var의 함정을 없애려고 도입된 것이다. 블록 스코프로 유효 범위를 좁히고, TDZ로 선언 전 사용을 막고, 재선언을 금지해 실수를 일찍 드러낸다. 그래서 새 코드에서는 var를 쓸 일이 사실상 없고, 기존 var 코드도 let·const로 바꾸면 예측 가능성이 올라간다.
💬 자주 묻는 질문 5가지
Q1반복문에서 왜 3,3,3이 나오나요?
var가 함수 스코프라 반복문 전체에 i가 하나뿐이기 때문입니다. setTimeout 콜백들은 그 하나의 i를 참조하는데, 콜백이 실행될 땐 이미 반복이 끝나 i가 3이죠. let으로 바꾸면 반복마다 새 i가 생겨 각 콜백이 자기 값(0,1,2)을 기억합니다.
Q2let과 const는 호이스팅이 안 되나요?
선언은 호이스팅됩니다. 다만 var처럼 undefined로 자동 초기화되지 않고 "초기화 전"(TDZ) 상태로 남아, 선언 줄 전에 접근하면 에러가 납니다. "끌어올려지되 초기화가 미뤄진다"가 정확한 표현입니다. var는 끌어올려지며 undefined로 채워지고요.
Q3이제 var는 아예 안 써도 되나요?
새 코드에서는 사실상 그렇습니다. let·const가 var의 함정(블록 유출·클로저·조용한 재선언)을 없애 주므로, var를 굳이 쓸 이유가 거의 없습니다. 기본은 const, 바뀌는 값은 let으로 충분합니다. 기존 var 코드도 점진적으로 바꾸면 안전해집니다.
Q4const는 값을 아예 못 바꾸나요?
const가 막는 건 변수의 재할당입니다. 다른 값으로 다시 대입하는 걸 막을 뿐, 그 값이 객체·배열이면 내부 속성 변경이나 push는 허용됩니다. 즉 const arr = []; arr.push(1)은 되지만 arr = [1]은 막힙니다. "연결은 고정, 내용은 변경 가능"으로 이해하면 됩니다.
Q5전역에서 var와 let 차이가 있나요?
있습니다. 일반 스크립트에서 var로 전역 선언하면 window(브라우저 전역 객체)의 속성이 되어 window.x로도 접근됩니다. let·const는 전역에 선언해도 전역 객체 속성을 만들지 않죠. 그래서 var는 의도치 않게 전역을 오염시키기 쉽습니다. 다만 ES 모듈이나 Node에서는 최상위도 모듈 스코프라, var여도 전역 객체 속성을 만들지 않는다는 점은 알아 두세요.
📌 결론
var와 let의 모든 차이는 스코프에서 갈린다. var는 함수 스코프라 블록을 뚫고, let은 블록 스코프라 예측 가능하다. 이 차이가 호이스팅(var는 undefined·let은 TDZ), 재선언(var는 조용히 덮어씀·let은 금지), 반복문 클로저(var는 하나 공유·let은 회차별) 함정으로 이어진다. 하나같이 let이 더 안전한 쪽이다.
실무 원칙은 다음과 같다.
📦 블록 스코프를 기본으로. let·const는 블록에 변수를 가둬 유출·오염을 막는다. var의 함수 스코프가 만드는 예측 불가함을 피한다.
🔁 반복문 카운터는 let. for의 카운터를 let으로 쓰면 반복마다 새 바인딩이 생겨 클로저 함정(3,3,3)이 사라진다.
✅ const 기본, var 지양. 재할당이 없으면 const, 바뀌면 let, var는 특별한 이유가 없으면 쓰지 않는다. ESLint no-var로 자동 점검한다.
let vs var 체크리스트
본 글은 자바스크립트 var·let 변수 선언 차이와 함정을 정리한 자료다. 코드 수정은 영향 범위를 확인한 뒤 신중히 적용한다.
#JavaScript #letvsvar #var #let #const #스코프 #블록스코프 #호이스팅 #TDZ #반복문클로저 #클로저 #ESLint #프론트엔드 #JS기초 #웹개발
'IT' 카테고리의 다른 글
| JS 에러 3대장 완전 정리 — TypeError·ReferenceError·SyntaxError 한눈에 (0) | 2026.07.02 |
|---|---|
| == vs === 완전 정리 — 느슨한 동등·엄격 동등·형 변환·NaN (0) | 2026.07.02 |
| this가 undefined — 바인딩·화살표 함수 원인·해결 (0) | 2026.07.02 |
| async/await 에러 처리 완전 정리 — try-catch·allSettled·전역 처리 (0) | 2026.07.02 |
| Invalid array length — 배열 길이 오류 원인·해결·예방 (0) | 2026.07.02 |
댓글