Hoisting

Javascript의 호이스팅

함수 안에 있는 선언들을 모두 끌어올려서 해당 함수 유효 범위의 최상단에 선언하는 것을 말한다.

자바스크립트 함수는 실행되기 전에 함수 안에 필요한 변수 값들을 모두 모아서 유효 범위(스코프)의 최상단에 선언하는 것 처럼 보인다.

자바스크립트 엔진은 코드를 실행하기 전에 실행 컨텍스트를 생성한다.

또한 자바스크립트 엔진은 코드를 실행하기 전 실행 컨텍스트를 위한과정에서 모든 선언(var, let, const, function, class)을 스코프에 등록한다.

이 때 자바스크립트 Parser가 함수 실행 전 해당 함수를 한 번 점검하면서 필요한 값들을 끌어올린다.

그렇다면 변수 선언을 굳이 미리 안해도 되는 것일까?

그렇지는 않다. let/const/class 를 이용한 선언과 *함수 표현식에서는 호이스팅이 발생하지 않는것 처럼 보인다.

그러나 모든 선언에 호이스팅이 발생하는 것은 맞다.

다만 let, const 키워드로 선언된 변수는 스코프의 시작에서 변수의 선언까지 일시적 사각지대(Temporal Dead Zone; TDZ)에 빠지기 때문이다.

여기서 중요한 지점은 이 호이스팅이라는 용어가 ‘선언이 먼저 메모리에 저장되었다.’는 것을 의미하기 때문에(선언이 끌어올려진다’는 의미이기 때문에)

모든 선언은 호이스팅이 일어난다는 말은 참이된다.

즉 호이스팅이 파일의 맨 위로 끌어올려지는 현상을 의미할 때 선언문 이전에 참조해서 에러를 발생시킨다고 호이스팅이 일어나지 않은 것은 아니라는 의미이다.

뭔가 이랬다 저랬다 하는 것 같으니 정리를 해보자.

다음의 명제들은 모두 참이다.

자바스크립트의 모든 선언에는 호이스팅이 일어난다.

let과 const는 초기화되지 않은 상태로 선언만 메모리에 저장된다.

선언은 끌어올려지고 (선언이 코드 실행 전에 메모리에 저장), 값이 초기화가 되지 않아 참조할 수 없어 에러가 발생한다.

호이스팅이 일어나지 않아서 에러가 발생하는 것이 아니라, 호이스팅이 일어났기 때문에 에러가 발생하는 것.

let foo = 1;

{
  console.log(foo); // Reference Error 
  let foo = 2;
}

위의 코드는 foo 변수의 선언이 호이스팅 되지 않았다면, 에러 없이 동작할 것이다.

왜? 스코프 체인에서 현재 스코프부터 상위 스코프로 이동하면서 foo를 찾을 것이기 때문이다.

그러나 현재 스코프에 foo 선언문이 존재하기 때문에, 호이스팅이 발생했으나 값이 초기화 되지 않아 에러가 발생한다!

console.log(a); // undefined
console.log(b); // Reference Error 
var a = 1;
let b = 1;

console.log(func); // [Function : func]

function func() {
 console.log("What is Hoisting?");
}

console.log(someFunc); // undefined

var someFunc = function () {
 console.log("What is Hoisting?");
}

console.log(arrowFunc); // undefined

var arrowFunc = () => {
 console.log("What is Hoisting?");
}

console.log(constFunc); // Reference Error

const constFunc = function () {
 console.log("Const Func");
}

자바스크립트 Parser 내부의 동작 방식을 유추해보자면 다음과 같을 것이다.

var a; // [Hoistring] 선언
console.log(a); // undefined;
console.log(b); // Reference Error 
a = 1; // 할당 
let b = 1; // Hoisting 발생 안함

함수 선언문에서의 호이스팅

함수선언문은 코드를 구현한 위치와 관계없이 자바스크립트의 특징인 호이스팅에 따라 브라우저가 자바스크립트를 해석할 때 맨 위로 끌어 올려진다.

/* 정상 출력 */
function printName(firstname) { // 함수선언문 
    var result = inner(); // "선언 및 할당"
    console.log(typeof inner); // > "function"
    console.log("name is " + result); // > "name is inner value"

    function inner() { // 함수선언문 
        return "inner value";
    }
}

printName(); // 함수 호출

==========================================================================

/** --- JS Parser 내부의 호이스팅(Hoisting)의 결과 - 위와 동일 --- */
/* 정상 출력 */
function printName(firstname) { 
    var result; // [Hoisting] var 변수 "선언"

    function inner() { // [Hoisting] 함수선언문
        return "inner value";
    }

    result = inner(); // "할당"
    console.log(typeof inner); // > "function"
    console.log("name is " + result); // > "name is inner value"
}

printName();

함수 표현식에서의 호이스팅

함수표현식은 함수선언문과 달리 선언과 호출 순서에 따라서 정상적으로 함수가 실행되지 않을 수 있다.

함수표현식에서는 선언과 할당의 분리가 발생한다.

  • 함수 표현식의 선언이 호출보다 위에 있는 경우

/* 정상 */
 function printName(firstname) { // 함수선언문
     var inner = function() { // 함수표현식 
         return "inner value";
     }
        
     var result = inner(); // 함수 "호출"
     console.log("name is " + result);
 }

 printName(); // > "name is inner value"

====================================================================

/* 정상 */
 /** --- JS Parser 내부의 호이스팅(Hoisting)의 결과 - 위와 동일 --- */
 function printName(firstname) { 
     var inner; // [Hoisting] 함수표현식의 변수값 "선언"
     var result; // [Hoisting] var 변수값 "선언"

     inner = function() { // 함수표현식 "할당"
         return "inner value";
     }
        
     result = inner(); // 함수 "호출"
     console.log("name is " + result);
 }

 printName(); // > "name is inner value"
  • 함수 표현식의 선언이 호출보다 아래에 있는 경우 (var 변수 할당)

/* 오류 */
 function printName(firstname) { // 함수선언문
     console.log(inner); // > "undefined": 선언은 되어 있지만 값이 할당되어있지 않은 경우
     var result = inner(); // ERROR!!
     console.log("name is " + result);

     var inner = function() { // 함수표현식 
         return "inner value";
     }
 }
 printName(); // > TypeError: inner is not a function

===========================================================================

/** --- JS Parser 내부의 호이스팅(Hoisting)의 결과 --- */
 /* 오류 */
 function printName(firstname) { 
     var inner; // [Hoisting] 함수표현식의 변수값 "선언"

     console.log(inner); // > "undefined"
     var result = inner(); // ERROR!!
     console.log("name is " + result);

     inner = function() { 
         return "inner value";
     }
 }
 printName(); // > TypeError: inner is not a function
  • 함수 표현식의 선언이 호출보다 아래에 있는 경우 (let/const 변수 할당)

 /* 오류 */
 function printName(firstname) { // 함수선언문
     console.log(inner); // ERROR!!
     let result = inner();  
     console.log("name is " + result);

     let inner = function() { // 함수표현식 
         return "inner value";
     }
 }
 printName(); // > ReferenceError: inner is not defined

Last updated