2.1 변수 선언 — var, let, const
변수란?
변수(variable)는 데이터를 저장하는 이름이 붙은 메모리 공간입니다. JavaScript에서는 var, let, const 세 가지 키워드로 변수를 선언할 수 있습니다.
var oldWay = "ES5 이전 방식";
let modernVar = "변경 가능한 변수";
const immutable = "변경 불가능한 상수";
var: 오래된 방식
특징
- 함수 스코프: 함수 내에서 선언되면 함수 전체에서 접근 가능
- 재선언 허용: 같은 이름으로 여러 번 선언 가능
- 재할당 허용: 값 변경 가능
- 호이스팅: 선언이 스코프 맨 위로 끌어올려짐 (값은
undefined)
// 함수 스코프
function example() {
if (true) {
var x = 10; // 블록이 아닌 함수 스코프
}
console.log(x); // 10 (블록 밖에서도 접근 가능!)
}
// 재선언 허용
var name = "김철수";
var name = "이영희"; // 오류 없음
console.log(name); // "이영희"
// 전역 스코프에서 var는 window 객체의 프로퍼티가 됨 (브라우저)
var globalVar = "나는 전역";
console.log(window.globalVar); // "나는 전역"
var의 문제점
// 1. 의도치 않은 전역 변수
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100);
}
// 출력: 5 5 5 5 5 (i가 루프 종료 후 5)
// 기대했던 출력: 0 1 2 3 4
// 2. 선언 전 사용 가능 (호이스팅으로 인한 혼란)
console.log(hoisted); // undefined (오류가 아님!)
var hoisted = "값";
let: 블록 스코프 변수
특징
- 블록 스코프:
{}블록 내에서만 유효 - 재선언 불가: 같은 스코프에서 재선언 불가
- 재할당 허용: 값 변경 가능
- TDZ: Temporal Dead Zone으로 선언 전 접근 불가
// 블록 스코프
function example() {
if (true) {
let x = 10; // 블록 스코프
console.log(x); // 10
}
console.log(x); // ReferenceError: x is not defined
}
// for 루프에서 올바른 동작
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100);
}
// 출력: 0 1 2 3 4 (각 반복마다 새로운 i)
// 재선언 불가
let name = "김철수";
let name = "이영희"; // SyntaxError: Identifier 'name' has already been declared
// 재할당은 가능
let count = 0;
count = 1; // OK
count++; // OK
const: 상수 (재할당 불가)
특징
- 블록 스코프: let과 동일
- 재선언 불가: let과 동일
- 재할당 불가: 선언 시 반드시 초기화, 이후 재할당 불가
- 객체/배열 내부 변경은 가능: 참조는 고정, 내용은 변경 가능
// 재할당 불가
const PI = 3.14159;
PI = 3; // TypeError: Assignment to constant variable
// 선언 시 초기화 필수
const x; // SyntaxError: Missing initializer in const declaration
// 객체 내부는 변경 가능!
const user = { name: "김철수", age: 25 };
user.name = "이영희"; // OK!
user.age = 30; // OK!
console.log(user); // { name: "이영희", age: 30 }
// 배열도 마찬가지
const arr = [1, 2, 3];
arr.push(4); // OK!
arr[0] = 10; // OK!
console.log(arr); // [10, 2, 3, 4]
// 완전 불변 객체 만들기: Object.freeze
const frozen = Object.freeze({ x: 1, y: 2 });
frozen.x = 100; // 조용히 무시됨 (엄격 모드에서는 TypeError)
console.log(frozen.x); // 1 (변경 안됨)
호이스팅(Hoisting) 완전 이해
호이스팅이란 JavaScript 엔진이 코드 실행 전에 변수/함수 선언을 해당 스코프의 맨 위로 끌어올리는 동작입니다.
var 호이스팅
// 실제 코드
console.log(x); // undefined (오류 아님!)
var x = 5;
console.log(x); // 5
// JavaScript가 실제로 해석하는 방식
var x; // 선언이 위로 끌어올려짐 (값은 undefined)
console.log(x); // undefined
x = 5; // 할당은 원래 위치에 유지
console.log(x); // 5
함수 호이스팅
// 함수 선언식: 완전히 호이스팅됨
hello(); // "Hello!" (선언 전에 호출 가능!)
function hello() {
console.log("Hello!");
}
// 함수 표현식: 변수만 호이스팅 (var의 경우)
greet(); // TypeError: greet is not a function
var greet = function() {
console.log("Hi!");
};
TDZ (Temporal Dead Zone)
**Temporal Dead Zone(TDZ)**은 let과 const로 선언된 변수가 선언 전에 접근할 수 없는 구간입니다.
// TDZ 예시
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 5;
// TDZ가 적용되는 구간
{
// ← TDZ 시작 (let/const 선언 전)
console.log(tdz); // ReferenceError!
let tdz = "값"; // ← TDZ 끝 (선언 완료)
console.log(tdz); // "값"
}
// 실수하기 쉬운 TDZ 예시
let a = 1;
{
console.log(a); // ReferenceError! (외부의 a가 아닌 내부 a의 TDZ)
let a = 2; // 이 선언 때문에 블록 전체에서 TDZ 발생
}
var vs let vs const 호이스팅 비교
| var | let | const | |
|---|---|---|---|
| 호이스팅 | O (undefined로) | O (TDZ) | O (TDZ) |
| 선언 전 접근 | undefined | ReferenceError | ReferenceError |
| 초기화 | 선언 시 undefined | 코드 도달 시 | 코드 도달 시 |
스코프 비교
// var: 함수 스코프
function varScope() {
var x = 1;
if (true) {
var x = 2; // 같은 변수를 덮어씀!
console.log(x); // 2
}
console.log(x); // 2 (if 블록이 영향 없음)
}
// let: 블록 스코프
function letScope() {
let x = 1;
if (true) {
let x = 2; // 새로운 변수
console.log(x); // 2
}
console.log(x); // 1 (if 블록의 x와 다른 변수)
}
실전 패턴: 언제 무엇을 쓸까?
// 규칙 1: 기본적으로 const 사용
const MAX_SIZE = 100;
const config = { debug: false, timeout: 3000 };
const users = [];
// 규칙 2: 재할당이 필요한 경우만 let
let count = 0;
count++;
let currentUser = null;
currentUser = fetchUser();
// 규칙 3: var는 사용하지 않음
// var는 레거시 코드에서만 볼 수 있어야 함
// 패턴: 반복문
for (const item of items) {
// const로 각 반복의 값을 받음
console.log(item);
}
for (let i = 0; i < 10; i++) {
// 인덱스 변경이 필요하면 let
}
// 패턴: 구조 분해 할당
const { name, age } = user;
const [first, ...rest] = array;
고수 팁
const를 쓰면 코드가 더 안전한 이유
// const로 선언된 변수는 의도치 않은 재할당 방지
const API_URL = 'https://api.example.com';
// API_URL = 'https://evil.com'; // TypeError: 즉시 발견!
// 함수도 const로 선언하는 것이 좋음
const calculateTax = (amount) => amount * 0.1;
// calculateTax = () => 0; // 의도치 않은 변경 방지
// 하지만 내부 변경은 여전히 가능하므로
// 완전 불변이 필요하면 Object.freeze 또는 라이브러리 사용
import { freeze } from 'immer'; // 깊은 불변성 라이브러리
스코프 체인
const global = '전역';
function outer() {
const outerVar = '외부';
function inner() {
const innerVar = '내부';
// 안쪽에서 바깥쪽 변수에 접근 가능 (스코프 체인)
console.log(global); // '전역' ✓
console.log(outerVar); // '외부' ✓
console.log(innerVar); // '내부' ✓
}
// 바깥에서 안쪽 변수 접근 불가
console.log(innerVar); // ReferenceError
inner();
}
outer();