렉시컬 환경으로 이해하는 var, let, const와 호이스팅
var, let, const의 차이를 이해하려면 렉시컬 환경(Lexical Environment)을 먼저 알아야 합니다.
흔히 var는 함수 스코프, let과 const는 블록 스코프라고 외우지만, 이것만으로는 다음 코드의 차이를 설명하기 어렵습니다.
console.log(a); // undefined
var a = 10;
console.log(b); // ReferenceError
let b = 10;
왜 같은 변수 선언인데 결과가 다를까요?
렉시컬 환경이란?
렉시컬 환경은 현재 스코프에서 사용하는 식별자와 값을 관리하는 공간입니다.
JavaScript는 코드를 실행할 때 각 스코프에 필요한 렉시컬 환경을 만들고, 변수를 이곳에서 관리합니다.
let name = "Kim";
function foo() {
let age = 20;
}
개념적으로 보면 다음과 같습니다.
전역 렉시컬 환경
├─ name → "Kim"
└─ foo → function
foo 렉시컬 환경
└─ age → 20
현재 렉시컬 환경에서 변수를 찾지 못하면 상위 렉시컬 환경을 확인합니다. 이것이 스코프 체인입니다.
그리고 중요한 점이 하나 있습니다.
블록에 들어간다고 새로운 실행 컨텍스트가 만들어지는 것은 아닙니다.
{
let value = 10;
}
블록에서는 새로운 렉시컬 환경이 만들어져 value를 관리합니다.
호이스팅은 무엇인가?
이제 호이스팅을 이해할 수 있습니다.
JavaScript는 코드를 실제로 실행하기 전에 선언된 식별자를 렉시컬 환경에 등록합니다.
console.log(a);
var a = 10;
실행 전에 a가 렉시컬 환경에 등록되기 때문에 선언문보다 먼저 a를 사용할 수 있습니다.
이처럼 선언이 코드의 실행 위치보다 먼저 처리되는 것처럼 보이는 현상을 호이스팅이라고 합니다.
그런데 var, let, const는 이 과정에서 차이가 있습니다.
var는 선언과 동시에 초기화된다
console.log(a); // undefined
var a = 10;
var는 실행 전에 식별자가 등록되고 undefined로 초기화됩니다.
실행 전
렉시컬 환경
└─ a → undefined
따라서 선언문 이전에 접근해도 ReferenceError가 발생하지 않습니다.
var a = 10;
이 코드는 개념적으로 다음과 같은 흐름으로 이해할 수 있습니다.
a 등록
↓
a = undefined
↓
코드 실행
↓
a = 10
let과 const는 초기화 시점이 다르다
let과 const 역시 호이스팅됩니다.
다만 var와 달리 선언문을 실행할 때까지 초기화되지 않습니다.
console.log(a); // ReferenceError
let a = 10;
실행 전에는 다음과 같은 상태입니다.
렉시컬 환경
└─ a → 초기화되지 않음
따라서 선언문 이전에 접근할 수 없습니다.
이처럼 let과 const가 선언된 위치에 도달하기 전까지의 구간을 TDZ(Temporal Dead Zone)라고 합니다.
let a = 10;
↑
│
TDZ 종료
const도 동일한 방식으로 동작합니다.
console.log(a); // ReferenceError
const a = 10;
차이는 const는 초기화 이후 재할당할 수 없다는 것입니다.
var, let, const의 차이
결국 세 가지 변수 선언 방식의 차이는 다음과 같이 정리할 수 있습니다.
var | let | const | |
|---|---|---|---|
| 스코프 | 함수 | 블록 | 블록 |
| 호이스팅 | O | O | O |
| 초기화 | undefined | 선언문에서 | 선언문에서 |
| 선언 전 접근 | undefined | ReferenceError | ReferenceError |
| TDZ | X | O | O |
| 재할당 | O | O | X |
핵심은 "let과 const는 호이스팅되지 않는다"가 아니라는 점입니다.
세 가지 모두 실행 전에 식별자가 처리되지만, 초기화되는 시점이 다르기 때문에 결과가 달라집니다.