JavaScript에서 변수를 선언하는 방식은 var, let, const 세 가지 키워드를 통해 이루어진다.
과거에는 var가 유일한 선택지였으나, ES6(ECMAScript 2015)부터 let과 const가 도입되면서 더욱 정교하고 안전한 변수의 관리가 가능해졌다. 이 글에서는 각 키워드에 대해 자세히 알아본다.
1. var - 이제는 놔줘야 한다
var은 JavaScript 초창기부터 사용된 변수 선언 키워드지만, 몇 가지 특징으로 인해 현대 JavaScript 개발에서는 사용을 지양하는 추세이다.
1.1. 함수 레벨 스코프 (Function-level Scope)
var로 선언된 변수는 함수 레벨 스코프를 가진다. 이는 변수가 선언된 함수 내에서만 유효하다는 의미다. 또한 코드 블록({})과는 관계 없이 함수 전체에서 접근이 가능하다.
다음 예제를 통해 자세히 확인해 보자.
위의 예시에서 알 수 있는 점은 var로 선언한 변수 a가 if 문 블록 밖에서도 접근이 가능하고, function 내에서만 접근 가능한 것 2가지이다.
var은 화살표 함수를 사용하더라도 화살표 함수 내에서만 유효하다.1.2. 변수 호이스팅(Hoisting)
var로 선언된 변수는 호이스팅(Hoisting) 이라는 현상을 겪는다. 이는 마치 변수 선언문이 해당 Scope(유효 구간)의 최상단 으로 끌어올려지듯 동작하는 것을 의미한다.
다음 예제를 보자.
예제에서 a가 선언되기 이전에 console.log()를 이용하여 출력을 했을 때, 에러가 아니라 값이 없음(undefined)을 출력했다.
이를 JavaScript 엔진의 입장에서 해석한다면 다음과 같다.
이처럼 var을 사용한 모든 변수를 미리 선언만한 뒤, 값을 할당하게 된다.
1.2.1. 0vs null vs undefined vs NaN
JavaScript에서는 값이 없거나, 숫자가 아님 등 여러 의미의 상태가 있다. 많은 사람이 혼란스러워하는데, 이는 최근 인터넷 커뮤니티에서 매우 쉽게 설명하고 있지만, 약간의 오해의 소지가 있어 이를 이용해 설명해본다.
0: 값이 0인 경우이다. 값이 들어있는 상태이고, 숫자 0이 들어있다.- 휴지 걸이에 다 쓰고 남은 휴지심만있는 상태
- 통장에 있던 돈을 다 써서 돈이 없는, 0원인 상태
- 숫자이기 때문에 여기서 더 빼면
-로 숫자가 감소할 수 있다.
null: 값을 일부러 비워둔 상태이다.- 휴지 걸이에서 휴지를 일부러 빼놓은 상태
- 통장이 비어있어야 한다고 적어둔 상황
- 의도적으로 비운 것이지만, 숫자 연산 시에는 0으로 취급된다.
undefined: 값이 들어있지 않은 상태이다.- 휴지 걸이는 있지만 휴지가 걸려있지 않은 상태
- 체크카드를 발급했지만, 아직 사용 승인이 나지 않아 사용할 수 없는 상태
- 아직 아무런 값도 없는 것이기 때문에 더 빼거나 더할 수도 없다.
NaN: 이상한 값을 숫자로 넣으려고 한 경우이다.- 휴지 걸이에 사포(?)를 걸어놓은 상태
- 통장에 돈이 아니라 돌을 넣으려는 경우
- 숫자가 아닌 값을 할당하게 되면 이러한 상태가 된다.
여기서 유의할 점은 undefined가 값을 주지 않아서 자연스럽게 된 것과 달리, null은 의도적으로 비워두었다는 것이다.
1.3. 재선언 및 재할당 가능
var는 이미 선언된 변수를 다시 선언하더라도 에러가 없고, 값의 재할당 또한 자유롭다.
이는 실수가 발생해도 오류없이 동작하고, 개발자의 의도와 다르게 동작할 수 있다는 의미이며, 프로그램의 동작을 예측하기 어려워짐을 시사한다.
위의 예제에서 알 수 있듯이, x를 선언해놓고, 다시 선언하더라도 문제없이 동작함을 알 수 있는데, 이는 다음과 같은 상황에서 더욱 문제가 될 수 있다.
위와 같이 중요한 값을 저장해놓고, 실수로 다른 값을 할당하면, 중요한 값이 소실된다.
이처럼 의도치 않은 실수를 유발할 수 있기 때문에, 현대 JS에서는 사용을 지양하는 것이다.
2. let&const: 새로운 표준
ES6에서 도입된 let과 const는 var의 단점을 보완하여 더 예측 가능하고, 안정적인 코드를 작성할 수 있도록 돕는다.
2.1. 블록 레벨 스코프 (Block-level Scope)
let과 const는 블록 레벨 스코프를 따른다. 이는 변수가 선언된 코드 블록({})에서만 유효하다는 것을 의미한다.
이는 굉장히 유용한데, if, for, while 문 등의 내에서 선언된 경우, 해당 구문 내에서만 유효하다는 뜻이며, 프로그래머가 의도한 대로 작동하기 쉽다는 것을 의미한다.
이러한 특징은 변수의 영향 범위를 명확하게 제한하고, 코드의 복잡도를 낮추며, 코드의 예측가능성을 높여준다.
2.2. 호이스팅과 TDZ (Temporal Dead Zone)
let과 const도 호이스팅은 발생하지만, var와는 다르게 동작한다. 변수의 선언은 var에서와 같이 스코프의 시작 부분으로 끌어올려지기는 하지만, let과 const로 선언된 변수는 초기화되지 전까지는 접근할 수 없다. 이 접근 불가능한 구간을 일시적 사각지대(Temporal Dead Zone) 이라고 한다.
이러한 TDZ는 변수가 선언되기 이전에 사용하는 것을 막아주기 때문에, var의 호이스팅으로 인해 발생가능한 혼란을 방지할 수 있다.
2.3. 재선언과 재할당
let과 const는 모두 재선언이 불가능하다. 실수로 잘못 선언하더라도, 오류가 발생하기 때문에 쉽게 알아챌 수 있다.
반면, let은 재할당이 가능하나, const는 불가능하다.
let으로 선언된 변수는 값을 재할당 할 수 있다.
const는 'constant(상수)`의 약자로, 상수를 선언한다. 즉, 재할당 없이 값을 변경할 수 없다. 따라서 선언과 동시에 반드시 초기화해야한다.
const는 변수 자체의 재할당을 막는 것이지, 변수에 할당된 객체나 배열의 내부 값(속성, 요소)을 변경하는 것까지 막지는 않는다. const는 변수가 가리키는 메모리 주소를 바꾸지 못하게 할 뿐, 그 주소에 저장된 객체의 내용은 수정할 수 있다.
즉, 내부의 값을 고정하는 것이 아니다.const 변수는 재할당할 수 없는가?
위의 스니펫에서 PI = 4라는 구문은 내부의 3.14159가 저장된 공간을 버리고, 4가 저장된 공간을 기억하라는 뜻이기 때문에, 내부의 값을 변경하는 것이 아니라 주소를 바꾸려는 시도가 된다. 이 때문에, 오류가 발생하는 것이다.다음과 같은 상황에서 const를 사용하면 더 안정적인 코드로 프로그래밍이 가능하다.
즉, 객체의 내부 값이나 **함수(메서드)**를 사용하고 변경하는 것은 가능하다.
3. 무엇을 사용해야 하는가?
현대 JavaScript 개발에서는 다음과 같은 규칙을 따르는 것이 좋다.
const를 기본으로 사용한다.
- 변수 값이 변경될 필요가 없는 경우,
const를 사용한다면 의도치 않은 값의 변경을 막고, 코드의 안정성을 높일 수 있다.
- 재할당이 필요한 경우에만
let사용한다.
for의 카운터 변수나, 특정 조건에 따라 값이 변경되어야 하는 변수에let을 사용하는 것이 적합하다.
var를 사용하지 않는다.
var는 호이스팅과 스코프(유효 범위) 문제로 인해 예기치 않은 버그를 유발할 가능성이 높다. 레거시(Legacy) 코드를 유지보수하는 경우가 아니라면,let과const의 사용이 바람직하다.
요약표
| 키워드 | 스코프(유효범위) | 호이스팅 | 재선언 | 재할당 |
|---|---|---|---|---|
var | 함수 레벨 | O (undefined) | O | O |
let | 블록 레벨 | X (TDZ 있음) | X | O |
const | 블록 레벨 | X (TDZ 있음) | X | X |
let과 const의 도입은 JavaScript를 더욱 견고하고 예측 가능한 언어로 만들어주었다. 이러한 키워드들의 차이점을 명확하게 이해하고 상황에 맞게 사용한다면, 더 나은 품질의 코드를 작성하는 개발자로 나아가는 한 걸음을 내디딘 것이다.