Hoisting(호이스팅)이란 무엇인가?

Hoisting(호이스팅)


Hoisting(호이스팅)이란, Javascript의 모든 선언문(var, let, const, function, function*, class)이 해당 Scope의 최상단으로 옮겨진 것처럼 행동하는 것을 말한다. C 언어 등 C-family 언어와는 다르게, Javascript의 모든 선언문은 ‘호이스팅’이 되는 Javascript만의 특징이 있다.


Hoisting에는 크게 2가지가 있다.

  • 변수 호이스팅
  • 함수 호이스팅


변수 호이스팅(Variable Hoisting)


변수 호이스팅이란, Javascript의 변수 선언문(var, let, const)이 해당 Scope의 최상단으로 옮겨진 것처럼 행동하는 것을 말한다. 즉, Javascript의 모든 선언문이 선언되기 전부터, 변수를 참조할 수 있게 된다.

1
2
3
4
5
6
7
console.log(num); // 1. -> undefined
var num = 100;
console.log(num); // 2. -> 100
{
var num = 5;
}
console.log(num); // 3. -> 5

위 코드를 처음 봤을 때, 첫줄의 출력 값은 Error가 날 것이라고 예상 할 수 있다. 그러나, 위 Javascript의 코드는 변수 호이스팅이 있기 때문에, undefined를 출력한다.

Javascript 엔진이 위 코드를 읽어들일 때, 제일 먼저 선언문부터 읽는다. 그리고, 그 중 제일 첫번째로 선언된 변수를 찾아, 그 변수를 선언하고, 초기화를 한다. 이때, 변수의 호이스팅 때문에, 선언문은 해당 Scope의 최상단, 즉 제일 윗줄로 옮겨진 것처럼 행동하여, ‘num’을 출력(console.log(num)) 하기 전에, 변수가 존재하는 것처럼 행동하고, 이 변수는 선언이 되고, 초기화까지 되었기 때문에(변수를 초기화하면, ‘undefined’를 메모리에 할당한다.), console.log(num)의 결과로는 ‘undefined’가 출력된다.

기본적으로, var 키워드로 선언된 변수는 선언 단계와 초기화 단계가 한번에 이루어진다. 변수가 선언이 될 때, 스코프에 Variable Object가 생성이 되고, 여기에 변수가 등록이 되어있어, 변수 선언문 이전에 변수에 접근해도 에러가 발생하지 않는다. 다만, 초기화 단계에서 변수는 메모리에 공간을 확보한 후, undefined를 저장하기 때문에, undefined를 반환한다. 이후, 변수 할당문(var num = 100;)에 도달하여 값의 할당이 이루어진다.

호이스팅 관점에서 다시 코드를 보자.

1
2
3
4
5
6
7
console.log(num); // 1. -> undefined
var num = 100;
console.log(num); // 2. -> 100
{
var num = 5;
}
console.log(num); // 3. -> 5

‘1.’이 실행되기 이전에 var num = 100;이 호이스팅되어 ‘1.’ 구문 위에(최상단) var num;가 옮겨진 것처럼 된다. 실제로 변수 선언이 코드 레벨로 옮겨 진것은 아니고, 변수 객체(Variable Object)에 등록되고, undefined로 초기화된 것이다. 그러나, 변수 선언 단계와 초기화 단계가 할당 단계와 분리되어 진행되기 때문에, 이 단계에서는 num에 undefined가 할당되어 있다. 변수 num에 값이 할당되는 것은 두번째 행(var num = 100;)에서 실시된다.

마지막 줄에서는 숫자 5가 출력이 되는데, 이는 코드 블록안에서 새롭게 변수가 중복 선언이 되어 num의 값이 5로 바뀌었기 때문이다. Javascript는 function-level scope를 갖기 때문에, 변수의 유효범위가 코드 블록에는 영향이 없다.


함수 호이스팅(Function Hoisting)


함수 호이스팅이란, Javascript의 함수 선언문(function)이 해당 Scope의 최상단으로 옮겨진 것처럼 행동하는 것을 말한다. 즉, Javascript의 모든 선언문이 선언되기 전부터, 함수를 호출할 수 있게 된다. 함수선언의 위치와는 상관없이 코드 내 어느 곳에서든지 호출이 가능하게 만드는 것을 말한다. 단, 함수선언식에만 적용이 된다.

함수선언식으로 정의된 함수는 자바스크립트 엔진이 스크립트가 로딩되는 시점에 바로 초기화하고 이를 VO(variable object)에 저장한다. 즉, 함수 선언, 초기화, 할당이 한번에 이루어진다. 그렇기 때문에 함수 선언의 위치와는 상관없이 소스 내 어느 곳에서든지 호출이 가능하다.

1
2
3
4
5
square(5); // 25

function square(number) {
return number * number;
}

다음은 함수표현식으로 함수를 정의한 경우이다.

1
2
3
4
5
var res = square(5); // TypeError: square is not a function

var square = function(number) {
return number * number;
}

위와 같이 에러가 발생한 이유는, 함수표현식의 경우는 함수 호이스팅이 아니라, 변수 호이스팅이 발생하기 때문이다.

함수표현식은 함수선언식과는 달리 스크립트 로딩 시점에 변수 객체(VO)에 함수를 할당하지 않고, runtime에 해석되고 실행된다. 즉, 선언과 초기화까지만 하여, undefined를 호출하는 것과 같기 때문에, 에러가 난다.

그래서, 더글러스 크락포드는 ‘함수표현식’만 사용할 것을 권한다. 왜냐하면, 에러가 나서 미리 호출을 방지하기 때문이다. 왠만하면, 선언문 이전에 호출하지 않는 것이 좋다.


변수와 함수의 호이스팅의 차이점:

  • 함수 호이스팅: 함수선언문 이전에 함수가 호출이 된다.(함수 선언식으로 함수를 선언했을 경우)
  • 변수 호이스팅: 함수표현식으로 함수를 선언했을 경우, 함수표현식 이전에 함수를 호출하면, 에러가 난다.
    • 왜냐하면, 함수표현식은 변수 호이스팅을 따르기 때문에, 할당은 안되고, 초기화까지만 되기 때문이다. 즉, 초기화까지만 되면 undefined를 호출하는 것과 같기 때문이다. undefiend() 이런형식으로 나온다는 의미이기 때문에, 에러가 난다.