본문으로 건너뛰기
Advertisement

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)**은 letconst로 선언된 변수가 선언 전에 접근할 수 없는 구간입니다.

// 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 호이스팅 비교

varletconst
호이스팅O (undefined로)O (TDZ)O (TDZ)
선언 전 접근undefinedReferenceErrorReferenceError
초기화선언 시 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();
Advertisement