Ray Book
실행 컨텍스트와 스코프

호이스팅의 실체

var, let, const, function이 각각 다르게 호이스팅되는 이유를 실행 컨텍스트의 생성 단계에서 찾습니다

javascripthoistingtdzvarletconst

호이스팅은 "끌어올림"이 아니다

많은 자료에서 호이스팅을 "선언이 코드 최상단으로 끌어올려지는 것"이라고 설명합니다. 하지만 실제로 코드가 물리적으로 이동하는 것은 아닙니다.

1편에서 다룬 실행 컨텍스트를 기억하세요. 모든 실행 컨텍스트는 생성 단계실행 단계 를 거칩니다. 호이스팅의 실체는 생성 단계에서 변수와 함수 선언이 먼저 처리되는 것 입니다.

네 가지 선언의 호이스팅

var, let, const, function — 네 가지 선언 키워드가 생성 단계에서 어떻게 처리되는지 시각화로 확인하세요.

코드생성 단계
1console.log(a);
2console.log(b);
3console.log(c);
4console.log(foo);
5 
6var a = 1;
7let b = 2;
8const c = 3;
9function foo() {}
변수 상태
vara
undefined
undefined
letb
TDZ
constc
TDZ
functionfoo
초기화됨
function
생성 단계. var a는 undefined로 초기화됩니다. let b, const c는 등록되지만 초기화되지 않습니다 (TDZ 진입). function foo는 함수 객체로 완전히 초기화됩니다.

정리하면:

키워드생성 단계선언 전 접근비고
varundefined로 초기화undefined 반환함수 스코프
let등록만, 초기화 안 됨ReferenceError (TDZ)블록 스코프
const등록만, 초기화 안 됨ReferenceError (TDZ)블록 스코프, 재할당 불가
function함수 객체로 완전 초기화정상 작동함수 스코프

TDZ — Temporal Dead Zone

letconst에서 발생하는 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 xundefined로 초기화된다면, 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()함수 표현식. barvar이므로 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

생성 단계에서:

  1. var xundefined로 등록
  2. function x → 함수 객체로 등록 (var를 덮어씀)

실행 단계에서:

  1. x = 1 → 함수를 1로 덮어씀

함수 선언이 var보다 우선순위가 높습니다.

블록 안의 함수 선언

주의: 블록 안의 함수 선언은 동작이 일관되지 않습니다.

if (true) {
  function greet() { return "hello"; }
}

greet(); // 브라우저에 따라 동작이 다를 수 있음

ECMAScript 명세에서는 블록 안 함수 선언의 동작을 명확히 정의하지 않고 구현체에 맡깁니다. 블록 안에서는 함수 표현식을 사용하세요:

if (true) {
  const greet = function() { return "hello"; };
}

V8에서의 호이스팅

엔진 시리즈에서 다뤘던 것처럼:

  • V8의 파서는 코드를 파싱하면서 각 스코프에 어떤 변수가 있는지 기록합니다
  • 이 정보를 기반으로 Ignition이 바이트코드를 생성할 때, 변수 생성과 초기화를 적절한 위치에 배치합니다
  • "호이스팅"은 코드 이동이 아니라, 파서와 컴파일러의 변수 등록 메커니즘 입니다

정리

호이스팅을 이해하는 가장 정확한 멘탈 모델:

  1. 코드 실행 전, 엔진이 선언을 훑으며 변수를 등록 합니다
  2. varundefined로, function은 함수 객체로 초기화 까지 합니다
  3. let/const등록만 하고 초기화는 선언문에 도달할 때 합니다 (TDZ)
  4. 실행 단계에서 코드가 위에서 아래로 실행됩니다

"코드가 올라간다"가 아니라 "등록이 먼저 일어난다"가 정확한 설명입니다.

다음 단계

마지막으로 남은 큰 주제 — this 바인딩입니다. 다음 글에서는 this가 어떤 규칙으로 결정되는지, 화살표 함수는 왜 다른지, bind, call, apply는 어떻게 동작하는지 살펴보겠습니다.