var, let, const
2022-07-30

JavaScript에서 변수를 선언하고 할당 받을 수 있는 키워드로 var, let, const 가 있다. ES5까지는 var 키워드가 유일한 변수를 선언할 수 있는 키워드였고 let과 const는 ES6에서 추가되었다.

var

var 키워드로 선언된 변수는 함수 스코프를 기준으로 동작한다. 즉, 함수 내에서 선언된 경우 해당 함수 내에서만 접근할 수 있다. 함수 외부에서 선언되면 전역 변수가 된다. 재할당재선언이 가능하다.

var a = 3;
var a = 5;
console.log(a); // 5;

이렇게 var 키워드로 변수를 선언할 경우에는 변수의 재선언과 재할당이 가능하기 때문에 예상하지 못한 버그를 발생시킬 수 있다.

let과 const 키워드는 var 키워드와 다르게 블록 스코프를 기준으로 동작하고 재선언이 불가능하다.

let

let 키워드로 선언된 변수는 재할당이 가능하지만 중복 선언은 불가능 하다. 재할당이 필요할 수 있는 변수에 사용한다.

// 재할당은 가능하다.
let a = 3;
a = 5;
console.log(a); // 5

// 재선언 할 경우 에러가 발생한다.
let b = 5;
let b = 7;
console.log(b); // SyntaxError

const

const키워드는 선언과 초기화를 동시에 해아하고 재선언과 재할당이 불가능하다.

재할당이 필요없는 상수를 선언할 때 사용한다.

// 선언후 초기화를 할 경우 에러가 발생한다.
const a;
a = 30;
console.log(a); // SyntaxError

// 재할당할 경우 에러가 발생한다.
const a = 3;
a = 5;
console.log(a); // SyntaxError

호이스팅 (hoisting)

호이스팅은 실행 컨텍스트 생성 시 렉시컬 스코프안에 있는 선언들을 모두 스코프의 최상단으로 끌어올리는 것 이라고 할 수 있다.

var 키워드로 선언한 변수는 선언초기화 단계를 한번에 실행한다. 그리고 이 두단계는 스코프의 최상단으로 끌어올려져 실행된다. 따라서 선언하기 전에 변수에 접근하여도 에러가 발생하지 않고 접근이 가능하다.

// var 키워드로 선언한 변수의 경우 호이스팅 시 undefined로 변수를 초기화 한다.
console.log(name); // undefined
var name = 'minjae';

let과 const 키워드로 선언한 변수는 var 키워드와 다르게 선언과 초기화 단계가 분리되어 실행된다. 선언 단계는 스코프의 최상단으로 끌어올려져 실행되지만, 초기화 단계는 선언문을 만나면 실행된다. 따라서 초기화되기 전에 변수에 접근을 하게되면 Reference Error가 발생한다. let과 const로 선언한 변수도 var처럼 호이스팅은 일어나지만 초기화 되기전까지 참조가 불가능하다. 이를 TDZ(Temporal Dead Zone) 에 있다고 하며 선언 전에 변수를 허용하지 않는 개념상의 공간이다.

// let 혹은 const로 선언한 변수의 경우는 에러가 발생한다.
console.log(gender); // ReferenceError
let gender = 'male'

var와 let, const를 잘 알고있어야 코드의 결과를 예측할 수 가 있다. 예를 더 들어보면 이렇다.

for(var i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
}

i라는 변수를 반복문을 통해 1씩 증가시키고 setTimeout 함수를 사용해서 i * 1초마다 출력하는 것을 기대했다. 하지만 결과는 5라는 숫자가 5번 출력이된다.

var 키워드의 경우 위에서 말했다시피 함수 스코프를 갖는다. 따라서 위의 코드에서 var는 함수스코프 내에 있지않고 호이스팅으로 인해 var 키워드로 선언된 변수의 선언과 초기화(undefined)가 실행컨텍스트의 최상위로 끌어올려져 전역변수가 된다.

이해하기 쉽게 코드를 변경해보면 이렇다.

var i;
for(i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i);
  }, i * 1000)
}

위 코드에서는 반복문이 종료된 이후 i의 값이 이미 5가 된 이후 비동기 함수인 setTimeout이 실행되어 i의 값을 참조한다. 그래서 5의 값이 5번 출력이 된다.

반면 let 키워드를 사용한 코드의 경우에는 블록 스코프를 가지기 때문에 반복문의 각 단계, i에 1을 더해가는 과정에 setTimeout이 i의 값을 참조하기 때문에 의도한 결과 값인 0, 1, 2, 3, 4가 출력되게 된다.