호이스팅 — 선언은 먼저 올라간다

선언하기 전에 쓴 변수가 왜 어떤 때는 undefined 고 어떤 때는 에러인지, 그 차이를 만드는 규칙.

Seobway · · 7분

왜 이게 필요한가

똑같이 "선언 전에 쓴" 코드인데 결과가 셋 다 다르다.

console.log(a);   // undefined      — 에러가 아니다?
var a = 1;

console.log(b);   // ReferenceError — 이건 에러다
let b = 1;

hello();          // "안녕"          — 아예 잘 돈다?
function hello() { console.log("안녕"); }

셋을 구분하지 못하면 "왜 여기선 되고 저기선 안 되지"를 평생 겪는다.


어떻게 동작하는가

자바스크립트는 코드를 위에서부터 한 줄씩 실행하기 전에, 스코프를 한 번 훑어
그 안의 선언을 미리 등록한다. 이 등록 단계 때문에 "선언이 위로 끌어올려진 것처럼"
보이는 것이 호이스팅이다.

핵심은 선언마다 등록되는 초기값이 다르다는 것이다.

`var`

등록되면서 곧바로 undefined 가 들어간다. 그래서 읽어도 에러가 아니다.

`let` · `const`

등록은 되지만 값이 없는 상태로 남는다. 이 구간을 읽으면 에러다.

`function` 선언

함수 몸통까지 통째로 등록된다. 그래서 위에서 바로 호출된다.

let · const 가 등록은 됐지만 아직 쓸 수 없는 그 구간을
TDZ(Temporal Dead Zone, 일시적 사각지대) 라고 한다.

인터랙티브 다이어그램: 스코프에 들어가서 코드가 끝날 때까지 (단계별로 재생할 수 있다)

  1. 스코프에 들어가면 먼저 선언부터 훑어 등록한다.
  2. var 는 등록되면서 undefined 가 들어간다.
  3. let 은 등록되지만 값이 없다 — 여기부터 TDZ 다.
  4. 함수 선언은 몸통까지 통째로 등록된다.
  5. 이제 한 줄씩 실행한다. a 를 읽으면 undefined 가 나온다.
  6. b 를 읽으면 TDZ 라서 ReferenceError 다.
  7. hello() 는 몸통이 이미 있으니 그냥 실행된다.
  8. let b = 1 줄에 도달하는 순간 TDZ 가 끝난다.

TDZ 는 실수를 빨리 드러내려고 일부러 만든 장치다. var 였다면
undefined 가 조용히 흘러다니다 한참 뒤 엉뚱한 곳에서 터진다.


직접 확인

주석을 하나씩 풀어 가며 어디서 에러가 나는지 확인해라.

console.log("var  :", typeof a);   // undefined — 에러가 아니다
var a = 1;

hello();                            // 몸통까지 올라가 있어 잘 돈다
function hello() { console.log("fn   : 안녕"); }

// 아래 두 줄의 주석을 풀면 TDZ 에 걸린다
// console.log("let  :", b);
// let b = 1;

// 함수 "표현식" 은 변수 규칙을 따른다 — 이것도 풀어 봐라
// bye();
// var bye = function () { console.log("bye"); };

마지막 것이 특히 헷갈린다. var bye = function(){}함수 선언이 아니라 변수 선언이다.
등록될 때 undefined 가 들어가 있으므로 bye()TypeError: bye is not a function 이다.


흔한 실수


한 줄 정리

실행 전에 선언이 등록되고, 등록될 때 들어가는 값이 var·let·function 마다 다르다.
let·const 는 값 없이 등록되며, 그 구간이 TDZ 다.

참고

  1. [1] Hoisting — MDN Web Docs 용어 사전
  2. [2] let — Temporal dead zone (MDN Web Docs)
  3. [3] let and const Declarations — ECMAScript 사양

관련 글