호이스팅은 "끌어올림"이 아니다
많은 자료에서 호이스팅을 "선언이 코드 최상단으로 끌어올려지는 것"이라고 설명합니다. 하지만 실제로 코드가 물리적으로 이동하는 것은 아닙니다.
1편에서 다룬 실행 컨텍스트를 기억하세요. 모든 실행 컨텍스트는 생성 단계 와 실행 단계 를 거칩니다. 호이스팅의 실체는 생성 단계에서 변수와 함수 선언이 먼저 처리되는 것 입니다.
네 가지 선언의 호이스팅
var, let, const, function — 네 가지 선언 키워드가 생성 단계에서 어떻게 처리되는지 시각화로 확인하세요.
정리하면:
| 키워드 | 생성 단계 | 선언 전 접근 | 비고 |
|---|---|---|---|
var | undefined로 초기화 | undefined 반환 | 함수 스코프 |
let | 등록만, 초기화 안 됨 | ReferenceError (TDZ) | 블록 스코프 |
const | 등록만, 초기화 안 됨 | ReferenceError (TDZ) | 블록 스코프, 재할당 불가 |
function | 함수 객체로 완전 초기화 | 정상 작동 | 함수 스코프 |
TDZ — Temporal Dead Zone
let과 const에서 발생하는 TDZ (Temporal Dead Zone, 일시적 사각지대) 를 자세히 봅시다.
{
// TDZ 시작 — 블록 진입 시점
console.log(x); // ReferenceError!
console.log(x); // 여기도 ReferenceError!
let x = 10; // TDZ 끝 — 선언문 도달 시점
console.log(x); // 10 (정상)
}TDZ는 시간 기반입니다:
- 시작: 스코프 (블록) 에 진입하는 순간
- 끝: 해당 변수의 선언문이 실행되는 순간
"Temporal"이라는 이름이 붙은 이유가 이것입니다 — 코드의 위치가 아니라 실행 시점 에 따라 결정됩니다.
TDZ가 존재하는 이유
var처럼 undefined로 초기화하면 편할 텐데 왜 TDZ를 만들었을까요?
let x = 1;
{
console.log(x); // ReferenceError! (바깥의 x가 아님)
let x = 2;
}만약 블록 안의 let x가 undefined로 초기화된다면, console.log(x)는 바깥의 1이 아니라 undefined를 출력할 것입니다. 이것은 혼란스럽습니다.
TDZ는 이런 조용한 버그를 방지 합니다 — 초기화 전 접근을 명확한 에러로 알려줍니다.
함수 선언 vs 함수 표현식
foo(); // "hello" — 함수 선언은 호이스팅됨
bar(); // TypeError: bar is not a function
function foo() { console.log("hello"); }
var bar = function() { console.log("world"); };function foo()— 함수 선언. 생성 단계에서 함수 객체로 완전히 초기화됩니다var bar = function()— 함수 표현식.bar는var이므로undefined로 초기화됩니다.undefined()를 호출하면 TypeError
baz(); // ReferenceError (TDZ)
const baz = () => console.log("arrow");const/let으로 할당된 함수 표현식이나 화살표 함수는 TDZ에 걸립니다.
호이스팅의 우선순위
같은 스코프에 함수 선언과 var 선언이 같은 이름으로 있으면?
console.log(x); // function x() {}
var x = 1;
function x() {}
console.log(x); // 1생성 단계에서:
var x→undefined로 등록function x→ 함수 객체로 등록 (var를 덮어씀)
실행 단계에서:
x = 1→ 함수를 1로 덮어씀
함수 선언이 var보다 우선순위가 높습니다.
블록 안의 함수 선언
주의: 블록 안의 함수 선언은 동작이 일관되지 않습니다.
if (true) {
function greet() { return "hello"; }
}
greet(); // 브라우저에 따라 동작이 다를 수 있음ECMAScript 명세에서는 블록 안 함수 선언의 동작을 명확히 정의하지 않고 구현체에 맡깁니다. 블록 안에서는 함수 표현식을 사용하세요:
if (true) {
const greet = function() { return "hello"; };
}V8에서의 호이스팅
엔진 시리즈에서 다뤘던 것처럼:
- V8의 파서는 코드를 파싱하면서 각 스코프에 어떤 변수가 있는지 기록합니다
- 이 정보를 기반으로 Ignition이 바이트코드를 생성할 때, 변수 생성과 초기화를 적절한 위치에 배치합니다
- "호이스팅"은 코드 이동이 아니라, 파서와 컴파일러의 변수 등록 메커니즘 입니다
정리
호이스팅을 이해하는 가장 정확한 멘탈 모델:
- 코드 실행 전, 엔진이 선언을 훑으며 변수를 등록 합니다
var는undefined로,function은 함수 객체로 초기화 까지 합니다let/const는 등록만 하고 초기화는 선언문에 도달할 때 합니다 (TDZ)- 실행 단계에서 코드가 위에서 아래로 실행됩니다
"코드가 올라간다"가 아니라 "등록이 먼저 일어난다"가 정확한 설명입니다.
다음 단계
마지막으로 남은 큰 주제 — this 바인딩입니다. 다음 글에서는 this가 어떤 규칙으로 결정되는지, 화살표 함수는 왜 다른지, bind, call, apply는 어떻게 동작하는지 살펴보겠습니다.