JavaScript 변수 선언 방식은 지난 20여 년간 꾸준히 발전해 왔습니다. 단순히 문법이 바뀐 것을 넘어, 이는 개발자가 코드를 더 예측 가능하고, 안전하며, 오류 없이 작성할 수 있도록 돕는 언어 설계의 근본적인 진화 과정이었습니다.
이 글에서는 문제투성이였던 var의 시대부터, ES6(ECMAScript 2015)에서 도입된 let과 const가 어떻게 현대 JavaScript의 표준이 되었는지 그 과정을 심도 있게 분석합니다.
1. 👶 var의 시대: 태초의 변수와 그 그림자
var는 JavaScript가 처음 탄생했을 때부터 존재했던 유일한 변수 선언 키워드입니다. 단순함 뒤에 숨겨진 var의 두 가지 치명적인 특성이 개발자들을 오랫동안 괴롭혀 왔습니다.
1.1. 스코프(Scope)의 혼란: 함수 레벨 스코프
var의 가장 큰 특징은 **함수 레벨 스코프(Function-level Scope)**를 갖는다는 것입니다.
- 문제점: for 루프나 if 문 같은 블록({}) 내부에서 var로 선언된 변수는 블록 밖에서도 유효합니다.
function varExample() { if (true) { var message = "Hello, world!"; } console.log(message); // "Hello, world!" (블록 밖에서도 접근 가능) }
- 부작용: 이로 인해 변수가 의도치 않게 재사용되거나 덮어씌워져 버그를 유발하는 경우가 빈번했습니다. 특히 복잡한 비동기 루프를 다룰 때(setTimeout 내부 등) 변수의 최종 값이 예상과 다르게 나오는 클로저(Closure) 버그의 주범이 되곤 했습니다.
1.2. 호이스팅(Hoisting)의 미스터리: 선언만 위로
var로 선언된 변수는 코드가 실제로 실행되기 전에 해당 스코프의 최상단으로 끌어 올려집니다. 이를 호이스팅이라고 합니다.
- 문제점: 변수의 선언은 호이스팅되지만, **초기화(값 할당)**는 원래 위치에 남아있습니다.
console.log(score); // undefined (에러가 나지 않고, '선언'만 호이스팅됨) var score = 100;
- 부작용: 개발자가 변수를 선언하기 전에 사용하더라도 ReferenceError와 같은 명확한 에러가 발생하는 대신, undefined라는 모호한 값으로 처리되어 코드를 분석하고 디버깅하기 매우 어렵게 만들었습니다.
2. 🚀 ES6 혁명: let과 const의 등장
2015년, ECMAScript 2015 (ES6) 표준이 발표되면서 var의 문제점을 해결하기 위한 두 가지 새로운 키워드, let과 const가 도입되었습니다. 이는 JavaScript의 변수 관리 방식을 완전히 뒤바꾼 혁명이었습니다.
2.1. let: 블록 레벨 스코프의 도입
let은 변수를 재할당할 수 있지만, var와 달리 **블록 레벨 스코프(Block-level Scope)**를 따릅니다.
- 해결책: if, for, while 등 중괄호({})로 묶인 모든 블록 내부에서 선언된 let 변수는 오직 그 블록 내에서만 유효합니다.
function letExample() { if (true) { let message = "Welcome!"; } console.log(message); // ReferenceError: message is not defined (정상적인 동작) }
- 이점: 개발자가 의도한 영역 밖에서 변수가 노출되거나 덮어씌워지는 일이 사라지면서 코드의 예측 가능성이 극적으로 향상되었습니다.
2.2. const: 불변성의 보장 (상수)
const는 let과 마찬가지로 블록 레벨 스코프를 따르지만, 변수를 선언할 때 반드시 초기화해야 하며, 선언 후에는 다른 값으로 재할당할 수 없습니다.
- 해결책: 한 번 할당된 변수의 값이 바뀌지 않음을 보장하는 **불변성(Immutability)**을 제공합니다.
const API_KEY = "abc12345"; API_KEY = "newKey"; // TypeError: Assignment to constant variable. (에러 발생)
- 이점: 코드의 안정성이 크게 높아졌습니다. 이 변수는 절대 바뀌지 않을 것임을 명시적으로 알려주므로, 코드 분석 시간을 줄이고 사이드 이펙트(Side Effect)를 방지하는 데 필수적입니다.
- 팁: 객체나 배열을 const로 선언할 경우, 변수 자체의 재할당은 막지만, 내부 속성이나 요소는 변경할 수 있다는 점을 유의해야 합니다.
2.3. let과 const의 호이스팅: TDZ의 도입
let과 const 역시 호이스팅됩니다. 하지만 var와 달리 초기화되기 전까지는 접근할 수 없는 영역이 있습니다. 이를 **TDZ (Temporal Dead Zone, 시간상 사각지대)**라고 부릅니다.
- 해결책: let이나 const 변수를 선언하기 전에 사용하려고 시도하면, undefined 대신 **ReferenceError**를 발생시킵니다.
console.log(age); // ReferenceError: age is not defined (TDZ로 인해 즉시 에러 발생) let age = 30;
- 이점: 개발자는 변수가 사용되기 전에 반드시 선언되었는지 확인하게 되므로, var의 모호한 undefined 문제를 방지하고 더 명확한 에러를 통해 빠르게 디버깅할 수 있습니다.
3. 🎯 모던 JavaScript의 변수 선언 가이드라인
오늘날 JavaScript 개발에서 var는 거의 사용하지 않으며, let과 const가 변수 선언의 표준으로 자리 잡았습니다.
현대적인 코드 스타일에서는 다음과 같은 원칙을 따르는 것이 좋습니다.
- 기본은 const로: 변수의 값이 변경될 필요가 없다고 판단되면 **무조건 const**를 사용합니다. 대부분의 변수와 참조는 변경될 필요가 없기 때문에 const가 기본값이어야 합니다.
- 재할당이 필요할 때만 let: 루프 카운터, 조건에 따라 값이 변경되는 변수 등 **반드시 재할당이 필요한 경우에만 let**을 사용합니다.
- var는 금지: ES6+ 환경에서 var는 사용하지 않는 것이 원칙입니다. 레거시 코드나 브라우저 호환성이 극히 제한적인 경우를 제외하고는 var를 피해야 합니다.
var에서 let과 const로의 진화는 단순한 문법 변화가 아니라, JavaScript 코드를 안전하고 예측 가능한 방식으로 작성하도록 유도하는 언어 철학의 발전입니다. 이 두 키워드를 숙달하는 것이 모던 JavaScript 개발의 첫걸음입니다.