호이스팅(hoisting)이란?

franchesca·2021년 8월 10일
2

JavaScript

목록 보기
7/7
post-thumbnail

개념

호이스팅(Hoisting)이란 자바스크립트 함수 안에 선언된 변수들을 유효 범위 최상단에 선언하는 것을 말합니다.

📍 어떻게 작동하나요?

  • 함수를 실행하기 전에 자바스크립트 Parser/Interpreter가 소스 코드를 훑으며 IR(Intermediate Representation)으로 읽습니다.
  • 이 과정에서 특정 키워드는 스코프를 형성하는데 (e.g. function), 이 때 새로운 'scope object'가 생성됩니다.
  • 각 스코프를 파싱하는 과정에서 특정 키워드(e.g. var)를 만나면 그것의 symbol 이름이 스코프 안에 만들어지고, 값은 undefined로 할당됩니다.
  • 즉, 자바스크립트는 함수를 실행하기 전에 함수를 훑으며 특정 키워드로 선언된 변수, 함수들을 모아서 유효범위의 최상단으로 끌어올립니다.
  • 이 때의 유효범위는 함수 블록 {}을 말합니다.

🤔 해당 변수, 함수가 실제로 작성한 코드의 상단으로 옮겨지는 것인가요?

  • 이것은 호이스팅에 대한 가장 흔한 오해이기도 합니다. 정답은, NO!
  • 호이스팅을 통해 변수, 함수 선언이 물리적으로 작성한 코드의 상단으로 옮겨지는 것은 아닙니다.
  • 변수 및 함수 선언은 컴파일 단계에서 메모리에 저장되지만, 코드에서 작성한 위치에 그대로 있습니다.

IR(Intermediate Representation : 소스 코드를 표현하기 위해 컴파일러 또는 가상 시스템에서 내부적으로 사용하는 데이터 구조 또는 코드입니다

참고:

📍 함수 안에 선언된 모든 변수가 호이스트되나요?

  • 아니요! var 변수 선언함수선언문에서만 호이스팅이 일어납니다.
  • 따라서 let/const 변수 선언과 함수표현식에서는 호이스팅이 발생하지 않습니다.
  • 또한, var 변수/함수의 선언만 위로 끌어 올려지고, 할당은 끌어 올려지지 않습니다.
  • 예시 (var 변수 vs let/const 변수)
  console.log("hello");
  var myname = "HEEE"; // var 변수 
  let myname2 = "HEEE2"; // let 변수 
  /* --- JS Parser 내부의 호이스팅(Hoisting)의 실행 순서 --- */
  var myname; // [Hoisting] "선언"
  console.log("hello");
  myname = "HEEE"; // "할당"
  let myname2 = "HEEE2"; // [Hoisting] 발생 X
  • 예시 (함수선언문 vs 함수표현식)
  foo();
  foo2();

  function foo() { // 함수선언문
          console.log("hello");
  }
  var foo2 = function() { // 함수표현식
          console.log("hello2");
  }
/* --- JS Parser 내부의 호이스팅(Hoisting)의 순서 --- */
var foo2; // [Hoisting] 함수표현식의 변수값 "선언"

function foo() { // [Hoisting] 함수선언문
        console.log("hello");
}

foo();
foo2(); // ERROR!! 

foo2 = function() { 
        console.log("hello2");
} // "할당"

예시 출처 및 참고: https://gmlwjd9405.github.io/2019/04/22/javascript-hoisting.html

🤔 함수 표현식이 var로 선언되어 있다면 어떻게 되나요?

  • 함수표현식에서는 선언과 할당의 분리가 발생합니다. 따라서 정상적으로 함수가 실행되지 않을 수 있습니다.
  • 예시
/* --- var 변수의 함수표현식 --- */
foo(); // TypeError: foo is not a function

var foo = function() {
			console.log("hello world")
}

/* --- let/const 변수의 함수표현식 --- */
foo2(); // ReferenceError: foo2 is not defined

let foo2 = function() {
			console.log("hello world2")
}
  • 첫번째 예시에서 'foo is not defined' 대신 'foo is not a function' 에러가 발생했는데, 이를 통해 함수표현식에서 선언과 할당의 분리를 확인할 수 있습니다.
  • foo가 선언은 되었으나 값이 undefined이기 때문에 'not a function'에러를 띄는 것입니다.
  • 반면 함수가 let/const에 할당되어 있을 경우 선언이 끌어올려지지 않아서 reference error가 발생합니다.

📍 호이스팅의 순서

같은 이름의 var 변수 선언과 함수 선언에서의 호이스팅

  • 변수 선언 > 함수 선언
  var myName = "hi";

  function myName() {
      console.log("yuddomack");
  }
  function yourName() {
      console.log("everyone");
  }

  var yourName = "bye";

  console.log(typeof myName);
  console.log(typeof yourName); 
  /** --- JS Parser 내부의 호이스팅(Hoisting)의 순서 --- */
  // 1. [Hoisting] 변수값 선언 
  var myName; 
  var yourName; 

  // 2. [Hoisting] 함수선언문
  function myName() {
      console.log("yuddomack");
  }
  function yourName() {
      console.log("everyone");
  }

  // 3. 변수값 할당
  myName = "hi";
  yourName = "bye";

  console.log(typeof myName); // > "string"
  console.log(typeof yourName); // > "string"

값이 할당되어 있지 않은 변수와 값이 할당되어 있는 변수에서의 호이스팅

  var myName = "Heee"; // 값 할당 
  var yourName; // 값 할당 X

  function myName() { // 같은 이름의 함수 선언
      console.log("myName Function");
  }
  function yourName() { // 같은 이름의 함수 선언
      console.log("yourName Function");
  }

  console.log(typeof myName); // > "string"
  console.log(typeof yourName); // > "function"
  • 값이 할당되어 있지 않은 변수의 경우, 함수선언문이 변수를 덮어쓰겠죠? 반대로 값이 할당되어 있는 변수의 경우, 변수가 함수선언문을 덮어씁니다.

예시 출처 및 참고: https://gmlwjd9405.github.io/2019/04/22/javascript-hoisting.html

📍 호이스팅과 Temporal Dead Zone의 관계?

우선 TDZ(Temporal Dead Zone)는 직역하면 '일시적 사각지대'라는 뜻으로, 선언 단계부터 초기화 시작 전까지의 구간을 말합니다. 이것이 호이스팅과 무슨 관련이 있냐구요?

앞서 함수표현식의 선언과 할당의 분리를 떠올린다면 조금 감이 올 수도 있을 것 같습니다. 하지만 조금 더 자세히 설명하자면 자바스크립트에서 변수가 어떻게 생성되는지에 대해 알아볼 필요가 있습니다.

변수 생성 3단계

자바스크립트에서 변수는 기본적으로 다음 3단계를 거쳐 생성이 됩니다.

  • 선언 단계 (Declaration phase)
    • 변수를 실행 컨텍스트의 변수 객체에 등록하는 단계를 의미합니다.
    • 이 변수 객체는 스코프가 참조하는 대상이 됩니다.
  • 초기화 단계 (Initialization phase)
    • 실행 컨텍스트에 존재 하는 변수 객체에 선언 단계의 변수를 위한 메모리를 만드는 단계입니다.
    • 이 단계에서 할당된 메모리에는 undefined로 초기화됩니다.
  • 할당 단계 (Assignment phase)
    • 사용자가 undefined로 초기화된 메모리에 다른 값을 할당하는 단계입니다.

실행 컨텍스트 : 코드가 실행되기 위해 필요한 환경

var, let/const 의 변수 생성 단계에는 어떤 차이가 있을까요?

  • 앞서 언급했듯이, 자바스크립트 interpreter는 내부에서 var라는 키워드를 만나면 이를 선언과 동시에 undefined 값을 할당합니다.
  • 즉, var는 선언과 동시에 초기화를 진행하며 메모리를 할당합니다.
  • 때문에 호이스팅이 될 때 var는 참조가 가능해서 오류가 발생하지 않습니다.
  • 반면, let/const의 경우 var와 달리 선언과 초기화를 따로 진행 합니다.
  • 그렇기 때문에 선언단계에서 실행 컨텍스트에 변수를 등록했지만, 메모리가 할당되지 않아 접근할 수 없어서 ReferenceError가 발생합니다.
  • 즉, 엄밀히 말하면 호이스팅은 되지만 접근할 수가 없어서 호이스팅이 되지 않는 것처럼 보이는 것이죠!

👿 그래서 호이스팅과 TDZ이 무슨 상관이냐구요!

즉, 엄밀히 말하면 let/const도 호이스팅이 된다는 말입니다. 하지만 let/const의 선언과 초기화가 따로 진행되면서 생기는 일시적 사각지대(TDZ)때문에 호이스팅이 돼도 참조할 메모리가 없어서 Reference Error가 발생하는 것이라는 거죠!

  • 호이스팅은 되지만 참조오류가 생기는 경우
    let, const, class구문, class construtor()내부의 super()메소드 (상속받은 경우), 기본 매개변수(ES6)
  • 호이스팅도 되고 참조오류도 생기지 않는 경우
    var, 함수 선언식, import

참고: https://yeonjewon.tistory.com/85

📍 호이스팅 사용 시 주의사항

  • 코드의 가독성과 유지보수를 위해 호이스팅이 일어나지 않도록 하는 것이 좋습니다.
  • 함수와 변수를 가급적이면 코드 상단부에서 선언함으로써 호이스팅으로 인한 스코프 꼬임현상 방지할 수 있습니다.
  • let/const, 함수표현식 사용을 권장하는 이유입니다.

Q: var를 쓰면 혼란스럽고 쓸모없는 코드가 생길 수 있습니다. 그럼 왜 var와 호이스팅을 이해해야 하나요?
A: ES6를 어디에서든 쓸 수 있으려면 아직 시간이 더 필요하므로 ES5로 트랜스컴파일을 해야합니다. 따라서 아직은 var가 어떻게 동작하는지 이해하고 있으면 좋겠죠?

profile
말하기 보다 생각하는, 생각하기보다 볼 줄 아는 개발자가 되려고 합니다✨

0개의 댓글