이전에는 변수를 선언할 수 있는 유일한 키워드가 var
였다.
var를 사용하여 선언한 변수는 초기화문의 유무에 따라 다르게 작동된다.
var x = 1;
var y = 2;
var x = 100; // 같은 스코프 내에서 중복 선언을 허용한다.
var y; // 무신된다.
console.log(x); // 100
console.log(y); // 2
x
의 경우, 두 번째 선언할 때에 초기값이 할당되었기에 x앞의 var 키워드가 없는 것처럼 동작하고, y
의 경우 두 번째 초기값이 할당되지 않았기에 선언문이 무시된 것처럼 작동된다.
그렇기에 이전에 선언한 변수의 이름을 그대로 사용할 경우, 이전에 할당한 값이 변경되거나 하는 문제가 발생한다.
또한, var
키워드로 선언한 변수는 오로지 함수의 코드 블록만을 지역 스코프로 인정한다.
var x = 1;
if(true) {
// 위의 x와 아래 x가 모두 전역 스코프이다.
// x가 중복 선언 되었으므로 x의 값이 변경된다.
var x = 10;
}
console.log(x); // 10
if문 안에서 선언한 x와 외부에서 선언한 x가 같은 전역 스코프이기에 중복선언이 된다.
따라서 원하지 않게 같은 변수 이름을 쓴다면, 외부에서 선언된 변수의 값이 변경된다.
var i = 10;
for(var i = 0; i < 5; i++) {
console.log(i); // 0,1,2,3,4
}
console.log(i); // 5
for문도 마찬가지로, for문 외부에서 선언한 i와 내부에서 선언된 i가 같은 스코프를 가져서 의도치않게 i의 값이 변경된다.
이처럼 함수 외의 경우에는 의도와 다르게 변수가 중복 선언된 것처럼 여겨지는 문제가 있다.
변수 호이스팅이란, 변수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 것을 의미한다.
var
키워드로 변수를 선언하면, 변수 호이스팅에 의해서 변수 선언문이 가장 앞으로 끌어올려진 것처럼 동작한다.
console.log(x); // ReferenceError가 아닌 undefined
x = 1;
console.log(x); // 1
// 변수 호이스팅으로 변수 선언문이 가장 위로 끌어올려진 것처럼 동작
var x;
이는 프로그램 흐름상 적절하지 않고, 가독성을 떨어뜨리기에 오류를 발생시킬 가능성이 높다.
위의 경우를 var 키워드에서 let과 const를 사용하는 경우에는 참조오류(ReferenceError)
가 발생한다.
위에서 살펴본 var의 단점(중복선언, 스코프 문제, 변수 호이스팅 문제)로 ES6에서는 let과 const로 새로운 변수 선언 키워드를 도입했다.
var의 경우에는 변수를 중복 선언하는 것이 허용되었지만, let
에는 syntaxError
가 발생한다.
let x;
let x; // "SyntaxError: Identifier 'x' has already been declared"
이는 const
에서도 같은데, let
과 const
는 같은 스코프 내에서는 변수 중복 선언을 허용하지 않는다.
let에서는 var과 다르게 모든 코드 블록을 지역 스코프로 인정한다.
let i = 1;
{
let i = 3;
let y = 100;
console.log(i); // 3
console.log(y); // 100
}
console.log(i); // 1
console.log(y); // ReferenceError: y is not definde
이처럼 let은 블록 단위로 지역 스코프를 가지며, 블록 외에서 선언된 외부 변수와 구분된다.
그렇기 때문에, 블록 단위로 함수를 구분하여 생각해야 한다.
let
키워드의 경우에는 변수 선언 전 참조를 할 경우, 참조 오류가 발생한다.
이는 var
의 경우 런타임 이전에 선언단계와 초기화 단계가 한번에 진행되기 때문인데, let
의 경우에는 선언단계와 초기화단계가 분리되어 진행된다.
javascript 엔진은 런타임 이전에 코드를 미리 읽기 때문에 변수 호이스팅 동작이 가능하다.
이 과정이 있기에 var
키워드 선언을 코드의 마지막 줄에 했어도 이미 코드를 미리 읽은 엔진이 변수 선언과 초기화 단계를 거쳐 undefined
값을 내는 것이다.
그러나, let
, const
키워드는 변수 선언문에 도달하기 전에 선언 단계는 실행되긴 하지만, 초기화 단계는 변수 선언문에 도달했을 때 실행된다.
때문에, 변수 선언문 전에 변수를 참조하게 되면 ReferenceError
가 발생한다.
//런타임 이전에 엔진은 이미 변수 선언단계를 실행했다.
//그러나 let 키워드로 선언되었기에 아직 초기화 되지 않았다.
console.log(x); //참조오류(ReferenceError) 발생
let x;
console.log(x); // undefined
x = 1;
console.log(x); // 1
스코프의 시작 지점인 첫 번째 console.log(x)부터 변수 x 선언까지는 참조 오류가 발생한다.
이 구간을 일시적 사각지대(Temporal Dead Zone:TDZ)
라고 부른다.
그렇다고 let 키워드에서 변수 호이스팅이 발생하지 않는 것은 아니다.
let 키워드에서도 변수 호이스팅은 발생한다.
const 키워드는 let 키워드와 비슷하다.
let과 같이 블록 레벨 스코프를 가지며, 변수 호이스팅이 발생하지 않는 것처럼 동작한다.
const 키워드는 반드시 선언과 동시에 값을 할당해야한다.
const x; // SyntaxError: Missing initializer in const declaration
const x = 1 // 반드시 선언과 동시에 초기화(값을 할당)해야한다.
const는 let, var과 달리 재할당이 금지되어 있다.
즉, 값을 한 번 할당하면 변경할 수 없다.
const x = 1;
const x = 2; //SyntaxError: Identifier 'x' has already been declared
const x = 1;
x = 100; // Assignment to constant variable.
재할당 없이는 값을 변경할 수 없는 특성을 이용해서 const 키워드는 상수를 표현하는데 이용한다.
이는 const 키워드로 선언된 변수에 원시 값을 할당한 경우 원시값은 변경할 수 없는 값이고, 그렇기에 재할당을 할 수 없는 const 값을 변경할 방법이 없는 것이다.
let의 경우에도 변수를 재할당으로 초기화하고 다른 메모리자리에 값을 넣어 교체하는 것일뿐, 직접적인 변경은 불가능하다.
하지만, 배열, 객체의 요소나 속성에 대한 수정은 가능하다.
const arr = [1,2,3];
arr.push(4); // [1,2,3,4]
그렇기에 대부분 const를 상수로 이용할 때에는 변수로 이용할 때와는 다른 상수 이름을 사용해주는게 일반적이다.
const MAX_VALUE = 10;
const로 선언된 변수는 변경할 수 없다.
다만, const 키워드로 선언된 변수에 객체를 할당하는 경우에는 값을 변경할 수 있다.
객체는 재할당없이 직접 변경이 가능하기 때문이다.
const myInfo = {
name: 'dobby',
age: 25,
}
myInfo.age = 24;
console.log(myInfo); // [object Object] {age: 24, name: "dobby"}
const가 변수 자체를 불변으로 만드는 것이 아니라, 변수가 참조 값의 변경을 막는 것이기 때문이다.
배열이나 객체는 참조 타입이고, const로 선언된 변수가 참조하는 메모리 공간 자체는 변경되지 않는다.
따라서 변수가 참조하는 배열이나 객체의 요소나 속성은 여전히 변경 가능하다.