BioPlayground

🧬
목록으로

let, var, const — 변수 선언의 차이

JavaScript의 var, let, const 세 가지 변수 선언 방식의 차이를 스코프와 재할당 관점에서 명확하게 설명합니다.

입문
|
8
|
검증 완료 (2026-07)
letvarconst변수 선언블록 스코프함수 스코프
진행률0/32 (0%)

let, var, const — 변수 선언의 차이

이 토픽을 마치면

var, let, const의 차이를 스코프와 재할당 관점에서 설명할 수 있고, 어떤 상황에서 무엇을 써야 하는지 판단할 수 있습니다.


세 가지 선언 키워드

JavaScript에서 변수를 선언하는 방법은 세 가지입니다:

javascript
var name = "철수";    // ES5 (옛날 방식)
let age = 25;        // ES6 (2015~)
const PI = 3.14159;  // ES6 (2015~)

셋 다 "값을 담는 이름을 만든다"는 점은 같습니다. 차이는 스코프(어디까지 보이는가)와 재할당(값을 바꿀 수 있는가)에 있습니다.


재할당 가능 여부

javascript
let count = 0;
count = 1;          // ✅ let은 재할당 가능

const MAX = 100;
MAX = 200;          // ❌ TypeError: Assignment to constant variable

const는 선언 후 재할당이 불가능합니다. 한번 넣은 값을 바꿀 수 없습니다. letvar는 재할당이 가능합니다.

주의: const는 "값이 불변"이 아니라 "변수 바인딩이 불변"입니다. 객체나 배열의 내부는 바뀔 수 있습니다:

javascript
const user = { name: "철수" };
user.name = "영희";  // ✅ 객체 내부 수정은 가능
user = {};           // ❌ 변수 자체를 다른 값으로 바꾸는 건 불가

스코프 — var vs let/const

var함수 스코프입니다. if문이나 for문의 블록({})을 무시합니다:

javascript
function example() {
  if (true) {
    var x = 10;
  }
  console.log(x);  // 10 — if 블록 밖에서도 접근 가능!
}

let/const블록 스코프입니다. 선언된 블록({}) 안에서만 존재합니다:

javascript
function example() {
  if (true) {
    let y = 10;
  }
  console.log(y);  // ReferenceError: y is not defined
}

블록 스코프가 직관적입니다. 선언한 중괄호 안에서만 살아있고, 밖에서는 사라집니다.


for문에서의 차이

javascript
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 출력: 3, 3, 3  — 모두 같은 i를 참조

var로 선언한 i는 for문 블록을 무시하므로, setTimeout이 실행될 때 이미 i는 3이 된 상태입니다.

javascript
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 출력: 0, 1, 2  — 각 반복마다 새로운 i

let은 각 반복마다 새로운 블록 스코프를 만들어서, 각각의 i가 독립적으로 존재합니다. 이것이 let과 var의 실질적으로 가장 중요한 차이입니다.


호이스팅

호이스팅(hoisting)이란 선언이 코드 최상단으로 끌어올려지는 현상입니다.

javascript
console.log(a);  // undefined (에러 아님!)
var a = 5;

console.log(b);  // ReferenceError: Cannot access 'b' before initialization
let b = 5;

var는 선언이 호이스팅되면서 undefined로 초기화됩니다. 그래서 선언 전에 접근해도 에러가 나지 않습니다 — 이건 버그를 찾기 어렵게 만듭니다.

letconst도 호이스팅되지만, 초기화 전까지 접근하면 에러를 냅니다. 이 구간을 TDZ(Temporal Dead Zone)라고 합니다. 에러가 나는 편이 디버깅에 유리합니다.


실무 가이드라인

javascript
// 1. 기본은 const
const API_URL = "https://api.example.com";
const users = [];

// 2. 값이 바뀌어야 할 때만 let
let count = 0;
count++;

// 3. var는 쓰지 않는다
// (레거시 코드에서만 볼 수 있음)
키워드스코프재할당호이스팅사용 시점
var함수undefined로 초기화쓰지 않음
let블록TDZ (에러)값이 변할 때
const블록TDZ (에러)기본값

모던 JavaScript에서는 const를 기본으로 쓰고, 재할당이 필요할 때만 let으로 바꿉니다. var는 역사적 이유로 남아있을 뿐, 새 코드에서는 쓸 이유가 없습니다.


클로저와 var의 유명한 버그

var의 함수 스코프가 만드는 가장 유명한 버그:

javascript
// 5개 버튼에 각각 다른 숫자를 보여주고 싶다
for (var i = 0; i < 5; i++) {
  document.getElementById("btn" + i).onclick = function() {
    alert(i);  // 전부 5를 alert
  };
}

모든 버튼이 5를 표시합니다. 클릭 시점에 i는 이미 5가 되었고, 모든 콜백이 같은 i를 참조하기 때문입니다.

javascript
// let으로 바꾸면 해결
for (let i = 0; i < 5; i++) {
  document.getElementById("btn" + i).onclick = function() {
    alert(i);  // 0, 1, 2, 3, 4
  };
}

let은 각 반복마다 새 스코프를 만들므로, 각 콜백이 자신만의 i를 가집니다. ES6 이전에는 IIFE(즉시 실행 함수)로 우회했는데, let의 등장으로 이 패턴이 불필요해졌습니다.


함수 선언에서의 var/let/const

함수를 만들 때도 선언 방식에 따라 차이가 있습니다:

javascript
// 함수 선언문 — 호이스팅됨
greet();  // ✅ 작동
function greet() {
  console.log("안녕");
}

// const 함수 표현식 — 호이스팅 안 됨
hello();  // ❌ ReferenceError
const hello = () => {
  console.log("안녕");
};

함수 선언문은 코드 어디서든 호출할 수 있지만, const로 선언한 함수는 선언 이후에만 호출할 수 있습니다. 프로젝트에서는 보통 한 가지 방식으로 통일합니다.


전역 스코프에서의 차이

javascript
// 브라우저 환경에서
var globalVar = "나는 var";
let globalLet = "나는 let";

console.log(window.globalVar);  // "나는 var" — window 객체에 추가됨
console.log(window.globalLet);  // undefined — window에 추가 안 됨

var로 선언한 전역 변수는 window 객체의 속성이 됩니다. 이것은 라이브러리 간 이름 충돌의 원인이 됩니다. letconst는 전역 스코프에서 선언해도 window에 추가되지 않습니다.


흔한 실수와 해결

javascript
// 실수 1: const 객체의 내부를 바꾸는 건 합법
const config = { debug: true };
config.debug = false;    // ✅ 가능 — 객체 내부 수정
config = {};             // ❌ 불가능 — 변수 재할당

// 실수 2: for문에 const를 쓰면
for (const i = 0; i < 3; i++) {  // ❌ i++에서 재할당 에러
  console.log(i);
}

// for...of에서는 const 사용 가능 (매 반복 새 바인딩)
for (const item of [1, 2, 3]) {  // ✅
  console.log(item);
}


Object.freeze — 진짜 불변 만들기

const 객체의 내부를 정말로 바꾸지 못하게 하려면:

javascript
const config = Object.freeze({
  apiUrl: "https://api.example.com",
  maxRetries: 3
});

config.apiUrl = "changed";    // 조용히 무시됨 (strict mode에서는 에러)
console.log(config.apiUrl);   // "https://api.example.com"

Object.freeze()는 객체의 속성 추가/수정/삭제를 막습니다. 단, 얕은 동결(shallow freeze)이라 중첩 객체의 내부는 여전히 변경 가능합니다.


코드를 읽을 때 const가 보이면 "이 값은 안 바뀌는구나"라고 바로 알 수 있습니다. 이 예측 가능성이 프로젝트가 커질수록 중요해집니다.