티스토리 뷰

이 글은 Rhino(Mozilla, Java)엔진을 기준으로 작성하였습니다.

 

# 호이스팅(Hoisting)이란?

호이스팅이란 코드가 실행되기 전(컴파일 시점), 선언을 실행 문맥(컨텍스트)에 저장하고 이후 코드가 실행되는 것을 말합니다. 즉, 선언 코드보다 초기화 등의 코드가 먼저 작성되어도 정상적으로 동작하는 것이죠.

 

선언이란 함수 선언(function, function*, class) , 변수 선언(var, const, let)을 말하며 이 중에서 const, let은 호이스팅의 대상이지만 TDZ(Temporal Dead Zone)의 제한으로 초기화 전까지 해당 변수에 접근할 수 없으며 class 호이스팅의 대상이 아닙니다.

 

종종 끌어올리다라는 표현 때문에 선언 코드가 물리적으로 상단으로 옮겨진 후 실행된다고 이해하는 경우도 있지만 정확히는 JS 엔진이 코드를 컴파일할 때, 선언된 식별자들을 실행 문맥에 저장합니다. 그 후 실행 단계에서 초기화가 이루어집니다.

이 행위를 끌어올리다라고 표현하는 것이며 코드 안에서 선언을 최상단에 작성한 것과 동일하게 동작합니다.

 

만약 선언이 함수 밖에서 선언되었다면 전역 문맥에, 함수 내에서 선언했다면 해당 함수 문맥에 저장됩니다.

 

※현재 실행되는 실행 문맥의 어휘적 환경을 스코프라고 합니다.


# 알아보기

선언과 초기화

JS는 선언만을 호이스팅합니다. 

선언은 JS 엔진이 코드를 컴파일하는 시점에 메모리에 저장되며 실행 시점에 이미 실행 문맥에 존재하게 됩니다.

 

먼저 변수 선언을 보겠습니다.

num = 1;
num += 2;
var num; 

console.log(num); // 3

num += 3;
console.log(num); // 6

위 코드는 정상 동작합니다. 컴파일 시점에 `var num`은 호이스팅되어 실행 문맥에 저장됩니다. 그 후 초기화가 이루어지기 때문에 콘솔과 같은 결과가 나오게 됩니다.


이해하기 쉽게 한 가지 예를 더 보겠습니다.

var x = 1;
console.log(x, y); // 1 undefined
var y = 2;

컴파일 시점에 `var x`와 `var y`가 실행 문맥에 저장됩니다. 그 후 초기화(`x =1`, `console.log(x, y)`, 'y = 2')가 이루어집니다.

 

위 예제를 이해하기 쉽게 풀어쓰면 아래와 같습니다. 컴파일 시 선언이 메모리에 저장된 후의 실행 흐름을 시각화한 것이며 이는 선언을 코드 최상단에 작성하여 실행할 때와 동일합니다. 컴파일 시 물리적으로 코드의 위치가 바뀌는 것은 아닙니다.

var x = undefined;
var y = undefined;

x = 1;
console.log(x, y); // 1 undefined
y = 2;

이번엔 함수 선언을 보겠습니다.

hoisting(); // This Function is hoisted!

function hoisting () {
  console.log('This Function is hoisted!');
}

hoisting함수는 선언 코드가 호출 코드보다 뒤에 있습니다. 하지만 함수 호출은 정상적으로 이루어집니다.

호출 코드가 실행되는 시점에는 hoisting함수가 이미 실행 문맥에 있기 때문입니다.


이번엔 함수 표현식에 대한 예를 보겠습니다.

hoisting(); // Uncaught TypeError: hoisting is not a function

var hoisting = function () {
  console.log('This Function is hoisted!');
}

마찬가지로 컴파일 시점에 `var hoisting`이 호이스팅됩니다. 하지만 초기화 코드가 실행되기 전에 함수를 호출하여 에러가 발생합니다.

 

 

TDZ (Temporal Dead Zone, 일시적 사각지대)

변수가 선언 되기 전, 접근하려고 할 때 ReferenceError를 발생시키는 영역을 말하며 const, let, class TDZ의 제약을 받습니다.

 

console.log(tdzLet); // Uncaught ReferenceError: Cannot access 'tdzLet' before initialization
let tdzLet;

변수 tdzLet이 초기화되기 전에 접근하려고 하니 TDZ의 제약을 받아 ReferenceError에러가 발생합니다.

참고로 `let tdzLet`는 var로 선언할 때와 마찬가지로 `let dtzLet = undefined` 와 동일합니다.


const tdzConst = 1
console.log(tdzConst); // 1

변수가 초기화된 이후에 접근하였으므로 정상적으로 작동합니다.


class도 마찬가지로 TDZ의 제약을 받습니다.

class childClass extends parentClass { // Uncaught ReferenceError: Cannot access 'parentClass' before initialization
}

class parentClass {
}

parentClass가 초기화되기 전에 상속받으려고 하니 ReferenceError가 발생합니다.

댓글