전체 목록
JavaScriptEasy#02

var, let, const의 차이점을 설명해주세요.

#JS#변수#스코프#호이스팅
힌트

스코프, 호이스팅, 재할당/재선언 가능 여부를 기준으로 비교해보세요.

정답 및 해설

var, let, const의 차이점을 설명해주세요.

ES6(ES2015) 이전에는 변수를 선언할 때 var만 사용할 수 있었습니다. ES6에서 letconst가 도입되면서 스코프, 호이스팅, 재할당/재선언 측면에서 더 예측 가능하고 안전한 변수 관리가 가능해졌습니다. 세 키워드의 차이를 명확히 이해하는 것은 버그를 예방하는 기초가 됩니다.

핵심 비교표

구분varletconst
스코프함수 스코프블록 스코프블록 스코프
호이스팅undefined로 초기화TDZ (초기화 전 접근 불가)TDZ (초기화 전 접근 불가)
재선언가능불가불가
재할당가능가능불가
전역 객체 프로퍼티됨 (window.x)안 됨안 됨

스코프(Scope)

var — 함수 스코프

var는 함수 단위로 스코프가 결정됩니다. if, for 같은 블록은 스코프를 만들지 않습니다.

function example() {
  if (true) {
    var x = 10; // 블록 안에서 선언했지만
  }
  console.log(x); // 10 — 블록 밖에서도 접근 가능
}

example();
// console.log(x); // ReferenceError — 함수 밖에서는 접근 불가

let / const — 블록 스코프

letconst{}로 감싼 블록 단위로 스코프가 결정됩니다.

function example() {
  if (true) {
    let y = 20;
    const z = 30;
  }
  // console.log(y); // ReferenceError
  // console.log(z); // ReferenceError
}

반복문에서의 차이

var와 블록 스코프의 차이가 가장 극명하게 드러나는 케이스입니다.

// var — 반복문이 끝난 후에도 i가 남아있음
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 출력: 3, 3, 3 (모두 같은 i를 공유)

// let — 각 이터레이션마다 새로운 블록 스코프 생성
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 100);
}
// 출력: 0, 1, 2 (각자 독립된 j를 가짐)

호이스팅(Hoisting)

var의 호이스팅

var로 선언된 변수는 컴파일 단계에서 선언부가 스코프 최상단으로 끌어올려지며, undefined로 초기화됩니다.

console.log(name); // undefined (에러가 아님!)
var name = 'Alice';
console.log(name); // 'Alice'

// 위 코드는 엔진 내부에서 아래와 같이 처리됨
// var name; // undefined로 초기화
// console.log(name); // undefined
// name = 'Alice';
// console.log(name); // 'Alice'

let / const의 TDZ (Temporal Dead Zone)

letconst도 호이스팅은 일어나지만, 초기화가 이루어지기 전까지 접근할 수 없는 구간이 존재합니다. 이를 **TDZ(일시적 사각지대)**라고 합니다.

// console.log(age); // ReferenceError: Cannot access 'age' before initialization
let age = 25;
console.log(age); // 25
// TDZ가 존재한다는 증거 — let은 호이스팅은 되지만 초기화 전 접근 불가
let x = 'global';

{
  // console.log(x); // ReferenceError (전역 x를 참조하지 않고 TDZ에 걸림)
  let x = 'block'; // 이 선언이 블록 상단으로 호이스팅됨 (TDZ 구간 시작)
  console.log(x); // 'block'
}

재선언과 재할당

var — 재선언, 재할당 모두 가능

var count = 1;
var count = 2; // 재선언 허용 (경고 없음)
count = 3;     // 재할당 허용
console.log(count); // 3

실수로 같은 변수를 두 번 선언해도 에러가 발생하지 않아 버그 추적이 어렵습니다.

let — 재선언 불가, 재할당 가능

let score = 100;
// let score = 200; // SyntaxError: Identifier 'score' has already been declared
score = 200; // 재할당은 허용
console.log(score); // 200

const — 재선언, 재할당 모두 불가

const PI = 3.14159;
// PI = 3.14; // TypeError: Assignment to constant variable
// const PI = 3; // SyntaxError

const와 참조 타입 (객체/배열)

const바인딩(참조) 을 불변으로 만들 뿐, 참조하는 객체의 내부 값까지 불변으로 만들지는 않습니다.

const user = { name: 'Alice', age: 25 };
user.age = 26;       // 가능 — 객체 내부 값 변경
user.job = 'Dev';    // 가능 — 프로퍼티 추가
// user = {};        // TypeError — 변수 자체에 새 객체 할당 불가

const nums = [1, 2, 3];
nums.push(4);        // 가능 — 배열 내부 변경
// nums = [5, 6];    // TypeError — 변수 자체에 새 배열 할당 불가

완전히 불변한 객체가 필요하다면 Object.freeze()를 사용합니다.

const config = Object.freeze({ apiUrl: 'https://api.example.com' });
config.apiUrl = 'https://other.com'; // 무시됨 (strict mode에서는 TypeError)
console.log(config.apiUrl); // 'https://api.example.com'

실무 가이드라인

언제 무엇을 써야 하나?

  1. const 우선: 값이 변경되지 않을 것 같으면 항상 const로 선언합니다. 재할당이 필요해질 때 let으로 바꾸면 됩니다.
  2. let 사용: 루프 카운터, 조건에 따라 변하는 값 등 재할당이 명확히 필요한 경우에 사용합니다.
  3. var 지양: 예외적인 경우(레거시 코드 호환, 함수 스코프가 명시적으로 필요한 경우)를 제외하고는 사용하지 않습니다.
// 좋은 예
const MAX_COUNT = 100;
const users = [];

for (let i = 0; i < MAX_COUNT; i++) {
  users.push({ id: i });
}

// 나쁜 예
var maxCount = 100;
var i;
for (i = 0; i < maxCount; i++) {
  // ...
}

정리

var가 가진 함수 스코프, undefined 초기화 호이스팅, 재선언 허용은 의도치 않은 버그의 주원인이었습니다. letconst는 이러한 문제를 해결하기 위해 도입된 것으로, 현대 JavaScript에서는 var 대신 let/const를 사용하는 것이 표준 관행입니다.