호이스팅

Created
April 4, 2024
Tags
JavaScript

호이스팅이란(Hoisting)

*인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미한다. 즉, 변수, 함수, 클래스 또는 임포트(import)의 선언문이 해당 스코프의 최상단으로 끌어올려지는 동작을 말하며, 코드 실행 전에 이러한 동작이 수행된다. 이는 JavaScript 엔진이 코드를 처리하는 동작 과정 중 하나이다. 실제로 코드가 끌어올려지는 것이 아니라, JavaScript 파서가 내부적으로 끌어올려 처리한다.

여기서 *인터프리터프로그래밍 언어의 소스 코드를 바로 실행하는 컴퓨터 프로그램 또는 환경을 말한다. 코드를 한 줄 한 줄씩 바로 실행해나가는 방식이다.

console.log(number); // undefined
var number; 

위 코드에서 출력되는 변수 name은 아직 선언되지 않았기 때문에 에러가 발생할 것 같지만 undefined가 출력된다. 선언된 변수 name은 코드 순서와 상관 없이 최상단에서 선언한 것처럼 끌어올려진다. 이것이 호이스팅이다.

console.log(number); // undefined
var number = 7;

또한, 변수의 선언만 끌어올려지므로 값을 할당해도 결과는 undefined이다.

호이스팅이 일어나는 이유

왜 일어날까? 이를 제대로 이해하기 위해서는 변수의 생성 단계TDZ에 대한 이해가 필요하다.

변수 생성 단계

JavaScript 엔진에서 변수는 선언 → 초기화 → 할당 단계를 거쳐 생성된다.

단계
설명
선언
코드에 변수 선언이 등장하면 JavaScript 엔진은 해당 변수를 *실행 컨텍스트(Execution Context)*환경 레코드(Environment Record)에 등록한다. var, let, const 키워드를 사용한 변수 선언일 것이다. 변수는 이 단계에서 메모리에 할당되지 않고, 단순히 존재한다는 사실을 JavaScript 엔진에게 알려준다.
초기화
선언 단계에서 생성된 변수의 메모리 공간이 생성되고 변수는 undefined로 초기화된다. 실제 메모리 공간을 확보하는 것으로 변수에 메모리 주소값이 저장되는 단계이다. 따라서, 메모리 참조를 통해 변수에 접근할 수 있다.
할당
초기화 단계에서 undefined로 초기화된 메모리에 다른 값을 할당하는 단계이다. 즉 변수에 값을 할당하면 초기화된 undefined 값이 대체되어 실제 유효한 값이 저장된다. 변수에 값을 할당할 때는 = 연산자나 다른 할당 방법을 사용한다.
🔍
실행 컨텍스트, 환경 레코드에 관한 내용은 실행 컨텍스트 페이지에서 자세히 살펴보겠다. 여기서는 호이스팅이 일어나는 원리와 관련된 부분 위주로 다루겠다. 간단히 설명하면 실행 컨텍스트는 코드가 실행될 때 필요한 정보를 담고 있는 환경이다.

var와 let, const

var로 선언한 변수와 let, const로 선언한 변수의 호이스팅은 차이점이 존재한다.

  1. var

var로 선언한 변수는 선언 단계, 초기화 단계가 동시에 진행된다. 선언 단계에서 변수 등록과 동시에 초기화 단계에서 메모리 공간도 할당 받아 undefined로 초기화되는 것이다. 그렇기 때문에 변수에 값이 할당되기 전에 접근해도 ReferenceError가 발생하지 않고, undefined가 반환된다.

코드를 통해 알아보자.

console.log(number); // undefined
var number = 7;
console.log(number); // 7

var로 선언한 변수는 선언 단계, 초기화 단계가 동시에 진행되기 때문에 number를 출력하면 undefined이 반환되고, 이후 할당 단계를 거쳐 7이라는 값이 반환되는 것이다.

더 자세하게는 아래와 같은 의미를 가진다.

var number;
console.log(number); // undefined
number = 7;
console.log(number); // 7

접근 가능해진 var로 선언한 변수는 호이스팅 되어 최상단으로 이동했고, 이때 선언과 초기화 단계가 동시에 일어났기 때문에 number를 참조하는 시점에 undefined가 이미 할당되어진 상태라 undefined이 반환되는 것이다. (참조 에러가 발생하지 않는다.)

  1. let, const

반면에 let, const로 선언한 변수는 선언과 초기화 단계가 분리되어 진행된다. 선언 단계에서 변수를 등록했지만, 메모리를 할당 받지 못해 접근할 수 없다. 그래서 초기화 이전에 변수에 접근하려고 하면 참조할 메모리가 없으므로 ReferenceError가 발생한다.

“잠깐.. 그럼 let, const로 선언한 변수는 호이스팅이 일어나지 않는 건가?” 라고 생각할 수 있다. 정답은 호이스팅 된다. 다만 호이스팅 되었으나 메모리가 할당되지 않아 접근할 수 없는 것이다. 이때 등장하는 개념이 TDZ이다.

TDZ(Temporal Dead Zone)

우리가 사용하려는 변수의 선언 ~ 초기화 사이의 구간을 가리키는 개념이다. 변수가 선언 되었지만 아직 메모리에 할당되지 않은 상태에서 변수에 접근할 때 발생한다. 이를 TDZ에 있다, 걸렸다고 얘기한다.

console.log(number); // Uncaught ReferenceError: number is not defined
let number = 7;

console.log(number); // Uncaught ReferenceError: number is not defined
const number = 7;
  • 변수 생성 단계에서 언급했듯이 var과 달리 letconst는 선언 단계와 초기화 단계가 분리되어 진행된다.
  • console.log(number);에서 변수 number에 접근하려고 할 때 초기화 되어 있지 않은 상태이기 때문에 ReferenceError가 발생하고, 이 참조 오류가 발생하는 구간이 TDZ이다.
    • letconst로 선언한 변수는 초기화 단계 이전까지 TDZ에 있다.
  • 결론적으로 let, const는 참조할 메모리가 없는 것이지 호이스팅이 일어나지 않는 건 아니다. 초기화되기 전까지 TDZ에 있기 때문에 호이스팅이 일어나지 않는 것처럼 보일 뿐이다.
💡
이제 호이스팅이 일어나는 이유를 알겠다. JavaScript 엔진은 코드가 실행되기 전에도 이미 해당 환경에 속한 코드 변수명, 함수 선언들을 알고 있기 때문에 호이스팅이 일어나는 것이다.
ㅎ씨 쉽지 않ㄴㅔ…
ㅎ씨 쉽지 않ㄴㅔ…

정리

  1. JavaScript 엔진은 코드가 실행하기 전에 필요한 환경을 설정하고 변수, 함수 등의 정보를 수집하여 코드 실행 시 참조할 수 있도록 한다. ⇒ 실행 컨텍스트 형성
  2. 이 과정에서 모든 선언(var, let, const, function, class)이 변수 객체 메모리에 저장된다.
  3. 코드 실행 전 이미 변수, 함수에 대한 선언이 저장되어 있기 때문에, 선언문보다 참조 및 호출이 먼저 나와도 동작할 수 있게 된다.
  4. 이는 선언 구문이 스코프 최상단으로 끌어올려지는 동작이며, 호이스팅이 일어났다고 한다.
  5. var과 달리 let, const 키워드를 사용한 변수에 접근하면 ReferenceError가 발생하고, 이 참조 오류가 발생하는 구간이 TDZ이다.
👈🏻 시작하며