
ES6에 들어서며 var의 문제점을 해결하기 위해 새로운 변수 선언 방법인 let, const가 등장했다. var, let, const의 특징과, var를 사용하면 안 되는 이유에 대해 알아보자.
var
ES6 이전의 변수 선언 방식으로, 재할당 및 재선언이 가능하다. 초기값을 주지 않으면 undefined가 들어간다.
var a; // undefined
a = 2; // 재할당
var a = 3; // 재선언
아래와 같이 여러 변수를 한꺼번에 선언할 수도 있다.
var a = 1, b = 2, c, d = a;
또한 var 키워드를 주지 않고 변수에 값을 할당하면, 암묵적으로 전역 변수를 생성한다.
a = 1;
console.log(a); // 1
function test() {
b = 2;
}
test();
console.log(b); // 2
단, strict 모드를 활성화했다면 변수를 생성하는 대신 에러를 띄워준다.
"use strict";
a = 1; // ReferenceError: a is not defined
console.log(a);
Scope
var는 Global Scope 또는 함수 Scope를 갖는다.
함수 내부에서 선언될 경우 해당 함수가 Scope가 되어 그 함수 밖에서는 접근할 수 없다.
함수 외부에서 선언될 경우 Global Scope를 갖게 되어 어디서든 접근할 수 있게 되고, window 객체에 property로 등록된다.
var a = 1; // Global Scope
console.log(a); // 1
console.log(window.a); // 1
function test() {
var b = 2; // Function Scope
console.log(b); // 2
}
console.log(b); // ReferenceError: b is not defined
Node.js 환경의 경우 조금 다르다.
Node.js엔 window 객체가 존재하지 않는다. window와 비슷한 global 전역 객체가 있지만, property로 등록되진 않는다.
또한 서로 다른 파일에서도 전역 변수가 공유되는 브라우저 환경과는 달리, Node.js의 전역변수는 해당 모듈 내에서만 공유된다.
호이스팅
자바스크립트 인터프리터가 코드를 해석할 때 변수의 선언부를 해당 스코프의 맨 위로 끌어올린다. 이때, var의 경우 초기값은 undefined가 된다.
// 호이스팅 이전
console.log(a);
var a = 2;
console.log(a);
// 호이스팅 이후
var a; // undefined 값 할당
console.log(a); // undefined
a = 2; // 2 할당
console.log(a); // 2
a가 선언되기 전에 참조했기 때문에 에러가 나야 할 것 같지만, 호이스팅으로 인해 a가 최상단에 선언됨으로써 에러 대신 undefined가 출력되게 된다.
좀 더 복잡한 예시를 들어보자.
console.log(a);
var a = 2;
function outer() {
console.log(`${a}${b}`);
function inner() {
console.log(`${a}${b}${c}`);
var c = 3;
}
var b = 2;
}
위 코드는 호이스팅 이후 이렇게 변형된다.
var a; // undefined
function outer() {
var b; // undefined
console.log(`${a}${b}`);
function inner() {
var c; // undefined
console.log(`${a}${b}${c}`);
c = 3;
}
b = 2;
}
console.log(a); // undefined
a = 2;
변수 a는 Global Scope에 해당하기 때문에 코드의 맨 위로 올라가고, b는 outer 함수의 스코프를 갖기 때문에 outer 함수의 맨 위로 이동한다. c도 b와 비슷하다.
또한 function도 호이스팅의 대상이 되기 때문에, outer 함수도 맨 위로 올라간다.
var의 문제점
여기까지 var의 특징에 대해 알아봤는데, 개발자를 헷갈리게 하는 몇 가지 문제점이 있다.
변수 중복 선언 문제
var score = 100;
// ...
var score = "A+";
// ...
if(score === 100) { // score 값이 "A+"가 됐기 때문에 false가 된다.
console.log("만점입니다!");
}
위와 같이 같은 이름의 변수를 재선언해도 에러가 뜨지 않고 정상적으로 동작하며, 그로 인해 착오를 일으킬 수 있다.
의도치 않은 변수 선언 문제
var type = "Fruit";
// ...
typo = "Vegetable"; // typo 변수가 없다는 에러 대신 새 변수를 생성한다.
if(type === "Vegetable") { // 항상 false
console.log("야채입니다.");
}
위 코드를 보면 개발자가 type를 typo라고 오타를 내버렸다. 하지만 자바스크립트 인터프리터는 여기서 에러를 띄우는 대신 새로운 전역변수 typo를 생성하고, 이로 인해 코드가 의도치 않게 동작하게 된다. (사실 이 문제는 var보단 strict 문제에 가깝다.)
너무 넓은 범위의 Scope
var의 scope는 Global 또는 Function이다. 다시 말해서, if문이나 반복문에서만 변수를 사용하고 싶어도 반복문을 벗어난 범위까지 scope로 지정되게 된다.
function test() {
for(var i=0; i<5; i++) {
console.log(i); // 0 ~ 4
}
console.log(i); // 5
}
test();
위 코드에선 i가 for문에서만 존재해야 할 것 같지만, Function Scope로 적용되기 때문에 test 함수 전체에서 사용할 수 있게 된다.
호이스팅 문제
console.log(a); // undefined
var a = 1;
선언되기 이전의 변수는 사용하지 못해야 정상일 것 같지만, 호이스팅으로 인해 undefined가 출력되며 코드가 정상적으로 실행된다. 이렇게 직관과 거리가 먼 코드는 버그를 불러일으킬 수 있다.
var에는 위와 같이 문제가 한두 가지가 아니었기 때문에, 이를 해결하기 위해 새로운 변수 선언 방법인 let과 const가 등장했다. 이들은 어떤 특징을 갖고 있는지 알아보자.
let
ES6부터 변수를 선언할 때 사용한다. 재할당이 가능하고 초기값을 주지 않을 시 undefined가 들어간다는 점은 var와 같지만, 같은 이름의 변수를 재선언할 수 없다는 차이가 있다.
let a; // undefined
a = 1; // 재할당
let b;
let b = 2; // SyntaxError: Identifier 'a' has already been declared
단, 블록이 다른 경우 같은 이름의 변수를 사용할 수 있다. 이때 변수를 참조할 시 자신과 같은 블록에 선언된 변수를 참조하게 된다.
let a = 1;
if(a === 1) {
let a = 2;
console.log(a) // 2
}
function test() {
let a = 3;
console.log(a); // 3
}
test();
console.log(a) // 1
Scope
let은 Block Scope를 갖는다. 함수뿐만 아니라 if, for, while, try/catch 등 코드 블록 내에서 선언된 변수는 블록 내부에서만 접근할 수 있다. Block 단위의 Scope로 범위가 좁혀지면서, var의 scope 문제가 해결되었다.
또한 전역 변수를 선언하더라도 window 객체의 property로 추가되지 않는다.
let g = 1;
console.log(window.g); // undefined
function test() {
let a = 1;
if(true) {
let b = 2;
}
console.log(b); // ReferenceError: b is not defined
for(let c=0; c<3; c++) {
let d = c;
}
console.log(c); // ReferenceError: c is not defined
console.log(d); // ReferenceError: d is not defined
try {
let e = 4;
}
catch {
console.log(e); // ReferenceError: e is not defined
}
}
console.log(a); // ReferenceError: a is not defined
호이스팅
let도 var와 같이 호이스팅이 된다. 하지만 var는 호이스팅 시 undefined로 초기화되는 반면, let은 시간상 사각지대 (Temporal Dead Zone, TDZ) 에 들어간다.
TDZ에 들어간 변수는 초기화 완료 시점까지 접근할 수 없게 된다.
function test() {
console.log(a);
}
a = 100;
let a = 1;
test();
위 코드는 호이스팅 시 아래와 같이 변환된다.
let a; // TDZ 진입
function test() {
console.log(a);
}
a = 100; // ReferenceError: Cannot access 'a' before initialization
a = 1; // TDZ 종료 및 1로 초기화
test(); // 1
호이스팅 시 undefined가 되는 var와는 다르게 let은 TDZ에 진입하며, TDZ에 들어간 상태인 a에 100을 할당하려 했기 때문에 ReferenceError가 발생하게 된다. test 함수의 console.log는 a를 참조하긴 하지만 실행은 a가 초기화된 이후에 되기 때문에, 에러가 발생하지 않는다.
const
ES6부터 상수를 선언할 때 사용하며, let과 Scope, 호이스팅 등의 특징이 동일하다.
단, 재할당이 가능한 let과는 달리 const는 재할당이 불가능하다. 때문에 let은 변수 선언 시 초기화하지 않아도 되지만, const는 반드시 선언과 동시에 초기화해야 한다.
const a; // SyntaxError: Missing initializer in const declaration
const b = 1;
b = 2; // TypeError: Assignment to constant variable.
const c = 3;
const c = 3; // SyntaxError: Identifier 'c' has already been declared
차이점 정리
var | let | const | |
재선언 | O | X | X |
재할당 | O | O | X |
스코프 | Global / Function | Block | Block |
호이스팅 | undefined | TDZ | TDZ |
var는 제약이 너무 적어 재선언, Scope 문제 등 다양한 의도치 않은 동작이 발생될 확률이 높다.
var 대신, 앞으로는 상수가 필요하다면 const, 변수가 필요하다면 let을 쓰도록 하자.
참고자료
'Study > Javascript' 카테고리의 다른 글
[Javascript] 비동기를 통해 프로그램의 효율 향상시키기 (Callback, Promise, async / await) (0) | 2023.05.13 |
---|---|
[Javascript] Array Method 정리 (0) | 2023.04.03 |
[Javascript] Number, Number.parseInt, parseInt의 차이 (0) | 2022.09.26 |