호이스팅(Hoisting)이 뭘까?

윤희준·2022년 3월 23일
0

JS

목록 보기
1/4

MDN Web Docs에 따르면, 호이스팅(hoisting)이란 인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것 이다.

대체 무슨 말인지 아리송하다. 좀 더 쉽게 풀어서 설명하자면, 호이스팅은 변수나 함수의 선언만 유효 범위의 최상단으로 옮기는 것 이라고 할 수 있겠다.

사전 지식

호이스팅에 대해 설명하기 전에, 먼저 아래 내용을 이해하고 넘어가자!

변수 생성 단계

자바스크립트는 3가지 단계를 거쳐 변수를 생성한다.

  • 선언 단계(Declaration Phase) : 변수 객체를 실행 컨텍스트에 등록한다.
  • 초기화 단계(Initialization Phase): 등록된 변수의 메모리를 확보한다. 이 단계에서 변수는 undefined로 초기화된다.
  • 할당 단계(Assignment Phase): 초기화된 변수에 실제 값을 할당한다.

let, const를 사용해 변수를 생성할 시에는 세 단계가 각각 따로따로 이루어지고,
var 을 사용해 변수를 생성할 시에는 선언 단계와 초기화 단계가 한번에 이루어지며,
함수 선언문 을 사용할 시에는 세 단계가 한 번에 이루어진다.

스코프의 시작 지점부터 초기화 시작 지점까지의 구간을 일시적 사각지대(Temporal Dead Zone, TDZ)라 한다.

유효 범위(variable scope)

변수에 접근할 수 있는 범위를 말한다.

  • 블록 스코프: 중괄호 {}로 감싸진 범위.
  • 함수 스코프: function(){}에서 중괄호 {} 내부의 범위. 즉 함수 코드 블록 내부의 범위.

자바스크립트는 기본적으로 함수 스코프 를 따르며, var로 변수를 생성할 시 함수 스코프 내에서 유효하다.

그러나 let, const로 변수를 생성할 시, 블록 스코프 내에서 유효하다.

호이스팅의 예시

자바스크립트보다 C나 Python과 같은 언어에 익숙한 사람이라면, 아래 결과가 퍽 이상하게 보일 것이다.

myName("철수"); // 첫 번째 myName 호출
myName(name); // 두 번째 myName 호출

function myName(name) {
  console.log("제 이름은 " + name + "입니다");
}
var name = "영희";

/*
결과:
"제 이름은 철수입니다"
"제 이름은 undefined입니다"
*/

myName 함수와 name 변수가 선언되기도 전에 사용되고 있다. 그러나 myName("철수")는 전혀 문제 없이 작동하며, myName(name) 역시 "영희" 대신 undefined를 출력할 뿐 오류 없이 동작한다.

/*자바스크립트 Parser 내부의 호이스팅 결과*/
var name = undefined; // 변수 선언

function myName(name) {
  console.log("제 이름은 " + name + "입니다");
} // 함수 선언

myName("철수");
myName(name);

name = "영희"; // 변수 할당.

var로 생성한 변수, 혹은 함수 선언에 호이스팅이 발생할 시 변수 및 함수 선언은 유효 범위 최상단으로 옮겨진다.

앞선 예제에서 myName(name) 호출 시 name 변수는 유효 범위 최상단에 이미 선언된 상태지만, name"영희" 라는 값은 아직 할당되지 않았기 때문에 undefined 가 출력되는 것이다.

더 많은 예제

아래와 같은 코드가 있을 때 무엇이 출력될 지, 어떤 변수가 호이스팅될 지 맞춰 보자.

/*자바스크립트 Parser 내부의 호이스팅 결과*/
var y = undefined;

x = 1;
console.log(x + " " + y);
y = 2;

"1 undefined" 가 출력되고, y만이 호이스팅된다. x처럼 var를 통한 선언 없이 바로 값이 할당될 경우, 자바스크립트 내부적으로는 x를 자동으로 선언한 다음 값을 할당한다. 그러나 이런 경우에는 호이스팅이 발생하지 않는다. var를 명시적으로 사용해 변수를 선언할 때만 호이스팅이 일어난다는 사실을 기억하자!

아래와 같은 코드는 어떨까.

a = "크랜";
b = "베리";

console.log(a + "" + b);

이 경우 "크랜베리" 가 출력된다. var를 사용한 선언 없이 변수를 바로 할당하고 있으므로 호이스팅은 발생하지 않지만, ab가 선언 및 할당된 이후에 console.log(a + "" + b)가 사용되므로 ab를 사용하는 데는 문제가 없다.

/*자바스크립트 Parser 내부의 호이스팅 결과*/
a = "크랜";
b = "베리";

console.log(a + "" + b);

그렇다면 아래와 같은 경우에는 어떨까?

function sum(a, b) {
  var x = add(a, b);
  return x;

  function add(c, d) {
    var result = c + d;
    return result;
  }
}

함수 선언문에서도 아래와 같이 호이스팅이 일어난다.

/*자바스크립트 Parser 내부의 호이스팅 결과*/
function sum(a, b) {
  var x = undefined;
  function add(c, d) {
    var result = c + d;
    return result;
  }

  x = add(a, b);
  return x;
}

함수 선언문의 경우 앞서 설명한 대로 선언, 초기화, 할당이 한 번에 이루어지기에, 함수 전체가 스코프 최상단으로 호이스팅되는 것을 확인할 수 있다.

마지막으로 함수 표현식의 경우 어떤 일이 일어날까?

function printName(firstname) {
  console.log(inner);
  var result = inner();
  console.log("name is " + result);

  var inner = function () {
    return "inner value";
  };
}
printName();

이 경우 TypeError가 발생한다. var result = inner()가 실행되는 순간, var로 생성된 inner변수는 선언 및 초기화 까지만 진행된 상태다. 따라서 inner는 아직undefined인 상태이기 때문에, 오류가 발생한다.

/** --- JS Parser 내부의 호이스팅(Hoisting)의 결과 --- */
function printName(firstname) {
  var inner = undefined;
  var result = undefined;

  console.log(inner); // undefined 출력
  result = inner(); // TypeError
  console.log("name is " + result);

  inner = function () {
    return "inner value";
  };
}
printName();

let과 const

호이스팅으로 인한 예상치 못한 오류의 발생을 피하기 위해, var 대신 사용되는 것이 letconst이다.

letconst를 사용할 때 역시 호이스팅은 일어난다. 그러나 앞서 설명했듯, letconst는 선언, 초기화, 할당이 각각 따로따로 일어난다.

호이스팅은 선언만을 스코프 최상단으로 옮기므로, 변수 스코프의 최상단부터 letconst가 위치한 지점까지는 일시적 사각지대 (Temporal Dead Zone, TDZ) 가 된다. 일시적 사각지대 내부의 변수들은 아직 초기화가 진행되지 않은 상태, 즉 메모리가 할당되지 않은 상태이므로 접근 시도시 ReferenceError를 발생시킨다.

function do_something() {
  //TDZ 시작
  console.log(bar); // undefined
  console.log(foo); // ReferenceError
  var bar = 1;
  //TDZ 끝
  let foo = 2;
}

이 때, TDZ 는 코드의 작성 순서가 아니라 코드의 실행 순서에 의해 형성된다는 사실을 기억하자. 아래 코드는 언뜻 보기에 letVar가 선언되기 이전에 const func = () => console.log(letVar) 에서 letVar 가 사용되고 있는 것처럼 보이지만, func 함수가 실행되는 지점이 TDZ 바깥이기 때문에 정상적으로 동작한다.

{
  const func = () => console.log(letVar);

  let letVar = 3; //TDZ 끝
  func(); // TDZ 밖에서 호출
}

참고:

profile
블로그 이전 작업 중입니다!

0개의 댓글