다음 질문에 대해 고민해보기 위해서 이 글을 작성하였습니다.
var
를 쓰는 것을 지양하라고 하는데, 그 이유가 무엇일까?var
대신 사용하라고 하는 ES6의 let
, const
는 var
와 무엇이 다를까?let
과 const
의 차이점과 let
보다 const
를 쓰라고 하는 이유는 무엇일까?var
var
는 좋게 말해 유연하다고 할 수도 있지만, 다음과 같은 특징 때문에 개발자의 실수를 유도한다.
var
키워드 없이 변수를 선언할 수 있다. -> 전역 변수 생성을 남발할 수 있다.// var 키워드 생략 가능
num = 1;
// 변수의 중복 선언 가능
var hobby = 'running';
var hobby = 'singing';
let color = 'blue';
let color = 'purple'; // Uncaught SyntaxError. var와 다르게 let, const는 중복 선언이 불가능하다.
var
로 선언한 변수는 선언 전에도 참조할 수 있다는 문제점이 있다. 이게 무슨 말이냐면, 아직 변수를 선언하기 전에 그 변수를 참조했을 때 어떤 에러도 발생시키지 않는다는 것이다.
-> 이 현상이 발생하는 이유는 호이스팅(문서 업로드 예정) 때문이다. 선언 전에 참조가 가능한 것은 프로그램의 흐름상 맞지 않고 가독성을 떨어뜨리며 예상치 못한 버그를 일으킬 수 있기 때문에 문제가 있다.
console.log(x); // undefined. x를 선언하기 전인데도 참조가 가능하다.
var x;
console.log(y); // ReferenceError: y is not defined. 반면 let으로 선언한 변수를 선언 전에 참조하면 에러가 발생한다.
let y;
- 함수 스코프(Function Level Scope): 함수 단위로 스코프를 지정한다. 함수 스코프를 따르는 경우, 함수 내에서 선언된 변수는 함수 내에서만 유효하고, 함수 외부에서 접근할 수 없다.
- 블록 스코프(Block Level Scope): 블록(중괄호
{}
) 단위로 스코프를 지정한다. 함수 블록을 포함해if
,for
,while
등이 모두 블록에 포함된다. 블록 내에 선언된 변수는 블록 내에서 유효하다.
var
키워드로 생성한 변수는 함수 스코프를 따른다. 그래서 함수 외부에서 생성한 var
변수의 경우 전역 변수가 되어 전역 변수 생성을 남발할 수 있다. 아래 예제를 보자.
var myList = [];
for (var i = 0; i < 5; i++) {
myList.push(function () {
console.log(i);
});
}
myList[0](); // 5
myList[3](); // 5
일단 for
문에서 선언한 i
는 for
문 안에서만 사용가능한 지역변수가 아닌 전역 변수가 된다. for
문은 함수가 아니고, var
로 선언한 변수는 함수 스코프를 따르기 때문에 i
는 전역에서 선언된거나 마찬가지이다.
for
문을 돌면서 myList
라는 배열에 함수를 추가하고 있다. 실행 결과를 예상해보자. 0, 1, 2, 3, 4를 출력하는 함수가 배열에 담긴걸까? myList
배열의 요소를 모두 실행해보면 모두 하나같이 5가 출력된다. 배열의 원소로 넘겨줬던 함수들은 호출될 때 for
문이 끝난 후 값이 5가 된 전역 변수 i
를 참조하기 때문이다. (우리가 예상한 결과를 출력하기 위해서는 var
를 let
으로 바꿔줘야한다.)
변수를 선언하는 키워드인 let
, const
가 var
의 문제점을 보완하기 위해 ES6에서 새로 추가되었다. let
, const
둘의 공통점을 먼저 알아보자.
var
와 달리 선언하기 전에 참조하면 에러가 발생한다.let foo = 123;
// 블록 스코프가 생성된다.
{
let foo = 456; // 지역 변수 👈 전역에서 선언된 변수 foo와는 다른 별개의 변수이다.
let bar = 456; // 지역 변수
}
console.log(foo); // 123
console.log(bar); // ReferenceError: bar is not defined 👈 전역에서는 코드 블록 내에 선언된 bar 변수를 참조할 수 없다. let으로 선언된 변수는 블록 레벨 스코프이기 때문이다.
💡
let
은 전역 스코프에서 선언되더라도window
객체(전역 객체, 브라우저의 최상위 객체)에 담기지 않는다.
undefined
가 자동으로 할당된다.let x; // undefined.
const x; // 에러. 변수 선언과 초기화가 한번에 되어야 한다.
const y = 10;
y = 20; // 에러. const로 선언된 변수는 재할당이 불가능하다.
프로그램 내에서 바뀌지 않는 값을 사용할 경우 const
는 유용하다. 개발자가 실수로 재할당을 통해 그 값을 변경하는 것을 막아주기 때문이다. 그래서 재할당이 필요없는 변수의 경우 let
이 아닌 const
로 선언하는 것이 좋다. 또한 const
를 사용하면 코드의 가독성과 유지보수성을 높일 수 있다. 아래 예제를 보자.
const TAX_RATE = 0.1; // 변수명을 대문자로 선언해 상수임을 명확히 한다.
let preTaxPrice = 100; // 세전 가격
let afterTaxPrice = preTaxPrice + preTaxPrice * TAX_RATE; // 세후 가격
console.log(afterTaxPrice); // 110
변경할 일이 거의 없는 TAX_RATE
라는 값을 const
변수로 선언했다. TAX_RATE
에 할당된 원시값(Number는 원시타입이다.)은 변경할 수 없는 값(불변성)이고 const
키워드에 의해 재할당이 금지되므로 할당된 이 숫자를 후에 변경할 수 있는 방법은 없다. 나중에 세율(TAX_RATE
)자체가 변경되어야하면 상수의 숫자 0.1
만 변경해주면 되기 때문에 유지보수성이 좋다.
💡 상수란?
재할당이 금지된 변수를 의미한다.(단 한번만 할당할 수 있는 변수) 상수≠변경 불가능한 값 (둘은 같은 의미가 아니다.)
💡 원시 타입, 리터럴(Literal), 불변성(Immutable)
const
변수에 재할당을 못한다고 해서 객체에 있는 값까지 바꿀 수 없다는 건 아니다. 아래 코드를 보자.
// const 변수에 원시 값을 할당한 경우 값을 변경할 수 없지만, 객체를 할당한 경우에는 값을 변경할 수 있다.
const person = {
name: 'Lee',
};
person.name = 'Kim'; // 객체는 변경 가능한 값이다. 객체는 재할당 없이도 값의 변경이 가능하기 때문이다.
const
키워드는 재할당을 금지할 뿐 불변을 의미하지는 않는다. const
로 선언한 객체를 변경하는 것이 가능한 이유는, 객체 내부 값이 변경되더라도 변수에 할당된 참조값은 변경되지 않기 때문이다.