자바스크립트 변수 선언 var, let, const 뭐가 다를까?
개발/javascript&typescript1 2025. 12. 18. 13:01JavaScript var, let, const 차이와 사용 기준 정리
자바스크립트 초보 때는 변수 선언이 그냥 "값 넣는 통" 정도로 느껴지는데, 프로젝트를 하다 보면 var / let / const의 차이가 생각보다 자주 문제를 만들기도 합니다.
특히 스코프(범위), 호이스팅(끌어올림), 재선언/재할당 가능 여부 때문에 코드가 "돌아가긴 하는데 불안한" 상태가 되는 경우가 많아서요.
이번 글에서는 세 가지 차이를 한 번에 정리하고, 실제로 어떤 기준으로 쓰면 좋은지까지 같이 정리해보겠습니다.
1) 한 줄로 먼저 결론
- const: 기본값(우선순위 1).
"재할당이 없어야 하는 값"에 사용합니다. - let: 필요할 때만(우선순위 2).
"재할당이 필요한 값"에 사용합니다. - var: 가능하면 피합니다(우선순위 3).
레거시 코드 유지보수 외에는 권장하지 않습니다.
2) 핵심 차이 비교표
| 항목 |
var | let | const |
| 스코프(scope) | 함수 스코프(function scope) | 블록 스코프(block scope) | 블록 스코프(block scope) |
| 재선언 | 가능 | 불가능 | 불가능 |
| 재할당 | 가능 | 가능 | 불가능 |
| 호이스팅 | 됨(초기값 undefined로 접근 가능) | 됨(하지만 TDZ 때문에 선언 전 접근 불가) | 됨(하지만 TDZ 때문에 선언 전 접근 불가) |
| 전역 객체(window/globalThis) 프로퍼티 | 전역 var는 붙는 경우가 많음 | 보통 안 붙음 | 보통 안 붙음 |
여기서 실무에서 가장 크게 체감되는 건 스코프와 재선언/재할당, 그리고 TDZ(Temporal Dead Zone)입니다.
3) 스코프 차이: var는 "함수 기준", let/const는 "블록 기준"
var: 함수 스코프라서 블록을 무시하는 느낌이 납니다
function test() {
if (true) {
var x = 10;
}
console.log(x); // 10 (if 블록 밖인데도 접근됨)
}
test();
let/const: 블록 스코프라서 더 예측 가능합니다
function test() {
if (true) {
let y = 10;
const z = 20;
}
console.log(y); // ReferenceError
console.log(z); // ReferenceError
}
test();
이 차이 때문에, var를 쓰면 “블록 안에서만 쓰려고 선언했는데 밖에서 살아있어 버리는” 상황이 생길 수 있습니다.
4) 호이스팅 차이: var는 선언 전에 접근하면 undefined, let/const는 에러
var: 선언 전에 읽혀버리는 게 문제를 숨깁니다
console.log(a); // undefined
var a = 1;
let/const: 선언 전에 접근하면 바로 에러(실수를 빨리 잡게 해줌)
console.log(b); // ReferenceError
let b = 1;
이 구간을 TDZ(Temporal Dead Zone)라고 부르는데, 쉽게 말해 “선언은 끌어올려지지만, 초기화 전에는 접근을 막는다”라고 이해하시면 됩니다.
5) 재선언/재할당 차이: const는 재할당 불가, let은 가능, var는 둘 다 가능(위험)
var는 재선언까지 가능해서 실수 유발이 큽니다
var n = 1;
var n = 2; // 에러가 안 남
console.log(n); // 2
let/const는 재선언이 막혀서 실수를 줄여줍니다
let m = 1;
let m = 2; // SyntaxError
재할당은 let만 가능
let count = 0;
count = count + 1; // OK
const pi = 3.14;
pi = 3.14159; // TypeError
6) const인데 객체/배열은 "내용 변경"이 가능하다는 점
여기서 const를 처음 쓰면 헷갈리는 부분이 하나 있습니다. const는 "값 자체를 바꾸는 것(재할당)"만 막고, 객체/배열 내부 변경은 막지 않습니다.
const user = { name: "kim" };
user.name = "lee"; // OK (객체 내부 변경)
user = { name: "park" }; // TypeError (재할당)
배열도 마찬가지입니다.
const arr = [1, 2, 3];
arr.push(4); // OK
arr = [1, 2]; // TypeError
정말 내부 변경까지 막고 싶으면 freeze를 쓰기도 하지만, 보통은 필요할 때만 고민하시면 됩니다.
7) 실무 사용 기준: 이렇게 정하면 깔끔합니다
기준 1) 기본은 const로 시작
- "이 값이 중간에 다른 값으로 바뀌어야 하나?"를 먼저 생각합니다.
- 아니라면 const가 기본입니다.
const API_URL = "https://example.com";
const userId = getUserId();
기준 2) 재할당이 필요하면 let
- 반복문 카운터, 상태값 누적, 조건에 따라 값이 바뀌는 변수 등에 사용합니다
let total = 0;
for (const price of prices) {
total += price;
}
기준 3) var는 새 코드에서 사용하지 않기
- 구형 코드(레거시) 유지보수 중 var를 마주칠 수는 있는데,
- 신규 작성에서는 var를 쓰는 순간 "스코프/호이스팅 관련 이슈"가 섞일 가능성이 커집니다.
8) 자주 나오는 실수 패턴 2개
(1) for문에서 var를 쓰면 비동기/클로저에서 꼬이는 경우
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 3 3 3
let을 쓰면 각 반복마다 블록 스코프가 분리돼서 기대대로 동작합니다.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 0 1 2
(2) 선언 전에 접근해도 에러가 안 나서 버그가 숨는 경우(var)
function run() {
if (!flag) {
// ...
}
var flag = true; // 선언이 끌어올려져서 flag가 undefined로 평가될 수 있음
}
이런 패턴은 let/const로 바꾸면 대부분 즉시 잡힙니다.
반응형




