Hoisting

신승준·2022년 10월 18일
0
post-thumbnail

자바스크립트에서 Hoisting이라고 불리는 현상에 대해 이것이 어떤 것이고, 무엇에 좋은지 뚜렷하게 알지 못한다고 생각하여 한 번 정리해보려 한다.

앞서 알아야 할 점은, Hoisting은 단지 자연스레 벌어지는 현상일 뿐, 자바스크립트를 만든 사람들이 편의성 등의 목적을 위해 따로 추가한 기능이 아니다. 따라서 ECMA 문서에도 Hoisting에 대해 뚜렷하게 설명해주는 글을 찾을 수 없었다.

Hoisting

마치 함수와 변수 선언이 코드의 최상단에 끌어올려져 있는 듯한 현상이라고, 설명하기 쉽게 많이 작성되어져 있지만, 실제로는 그저 자바스크립트 엔진이 코드를 평가하면서 식별자를 미리 환경 레코드(Environment Record)에 등록하면서 자연스럽게 생기는 현상이다. Hoisting 관련 글들을 보면, Hoisting이라는 현상이 진짜로 변수 선언문이 위로 끌어올리는 것이 아님을 잘 설명해준다.(실행 컨텍스트, 환경 레코드에 대해 잘 설명된 영상)

자바스크립트 엔진은 코드 평가 단계에서 실행 컨텍스트를 생성하고, 코드 내의 선언문들을 파악하여 실행 컨텍스트 내의 환경 레코드에 식별자들을 등록시킨다.

따라서 런타임, 즉 코드 실행 단계에서 선언문보다 변수 사용이 앞서 오더라도 에러가 발생하지 않을 수 있다.(var 혹은 function 키워드로 선언된 함수를 실행할 경우)

다음 예시를 보자.

console.log(temp);

var temp = 'hello!';

temp가 선언되기 전임에도 불구하고 temp를 찍는 console.log를 실행시키면 에러가 발생하지 않는다. 이는 코드 실행 전에 코드 평가 단계에서 temp라는 식별자를 해당 변수의 범위인 실행 컨텍스트의 환경 레코드에 등록하였기 때문이다. 그리고 var로 선언했기 때문에 식별자가 환경 레코드에 등록됨과 동시에 값까지 할당되는데, 이 때 undefined가 할당된다.


식별자

const temp = 1;

위에서 temp가 식별자이다. 즉 변수의 이름을 식별자라고 칭한다. 변수는 데이터가 담길 수 있는 공간을 뜻한다. 식별자는 단지 변수명일 뿐이다. 위의 코드를 비롯해 설명하자면, 1이 temp라는 공간, 여기서 temp는 변수의 이름인 식별자이고 temp라는 공간은 변수라고 할 수 있다.


그럼 let과 const는?

ES6부터 var의 단점을 극복하기 위해 let과 const가 추가되었다. let과 const는 코드 평가 단계에서 식별자를 등록하지만 var처럼 undefined를 할당하지 않는다. 따라서 var와는 다르게 선언 이전에 참조하려고 하면 Reference Error가 발생한다. 아무것도 할당되어 있지 않기 때문이다.(이렇게 변수에 값이 할당되어 있지 않아 참조할 수 없는 구간 혹은 영역을 TDZ(Temporal Dead Zone)이라고도 한다) 값이 할당되는 코드를 만나고 나서야 비로소 참조가 가능해진다.

console.log(temp); // Reference Error

// 밑의 const temp = 1;을 만나기 전까지는 temp를 참조할 수 없다. 여기가 TDZ이다.

const temp = 1;

console.log(temp); // 1

그리고 아주 쬬오뀸 특별한 예시가 있는데 한 번 보자!

const hello = 100;

function print1() {
  console.log(hello); // undefined
  
  var hello = 200;
}

function print2() {
  console.log(hello); // Reference Error
  
  const hello = 300;
}

console.log(hello); // 100;
print1();
print2();

print2에서는 스코프 체이닝에 의해 print2 함수 내에서 선언된 hello를 참조하려 한다. 바깥의 hello가 아니라! 이 때는 앞서 설명한 것과 동일한 흐름으로, print2 실행 컨텍스트의 환경 레코드에 hello라는 식별자만 등록이 되어 있어(값은 할당되어 있지 않고) 참조하려고 하면 Reference Error가 발생한다.


그럼 함수는?

이 때는 function 키워드로 선언을 하느냐 혹은 let이나 const, var를 이용해 선언을 하느냐에 따라 다르다.

function 키워드로 선언을 했다면?

이 경우는 코드 평가 단계에서 식별자를 실행 컨텍스트의 환경 레코드에 등록시킴과 동시에 정의한 함수 기능을 수행할 수 있는 함수 객체까지 만들어 할당시킨다. 따라서 함수 선언문이 아래에 있더라도 위에서 함수를 실행시킬 수 있다.

temp(); // hello

function temp() {
  console.log('hello');
}

let, const, var 등을 이용해 함수를 선언했다면?

JavaScript에서는 함수를 변수에 할당시킬 수 있다는 것을 아주 잘 알 것이다. 이 때는 앞서 설명한 let, const, var와 동일하게 동작한다.

temp1() // Reference Error
temp2() // Reference Error
temp3() // TypeError: temp3 is not a function
console.log(temp3); // undefined

const temp1 = () => {
  console.log(temp1);
}

let temp2 = () => {
  console.log('temp2')
}

var temp3 = () => {
  console.log('temp3');
}

temp1, temp2는 모두 코드 평가 단계에서 식별자만 등록될 뿐 값이 따로 초기화되진 않는다. 따라서 위와 같이 선언문 이전에 사용하면 Reference Error가 발생한다. 반면 temp3도 사용할 수 없는데 이는 undefined가 할당되어 있기 때문이다. undefined()는 안되지 않는가? 함수가 아니니까..! 그리고 temp3를 출력해보면 undefined가 출력되는 것도 확인할 수 있다.

나는 const로 함수를 만드는 것이 더 좋다고 생각한다. function 키워드로 만들면 선언 이전에 함수를 실행시킬 수 있는데, 이러면 좀 일관성이 떨어지지 않을까라는 생각이 들어서이다! 다른 사람들이랑 협업하다보면 누군가는 function 선언문 이전에 함수를 실행시킬 수도 있을 것이다... 따라서 그냥 const로 함수를 만들고 이후에 실행할 수 있도록 일치시키는게 좋지 않을까라는 생각을 한다!

그렇다면 왜 호이스팅이라는 단어를 만들어가면서까지 설명을 하려 할까? 이게 필요한 이유가 있나?

function main1() {
  // 1
  function add(n1, n2) {
    return n1 + n2;
  }
  
  ...
  
  const value = add(3, 4);
  
  // 2
  function add(n1, n2) {
    return n1 + n2;
  }
}

function main2() { ... }

function main3() { ... }

// 3
function add(n1, n2) { ... }

// 4
export function add(n1, n2) { ... }

코드를 어떻게 나눠야 할까요? (feat. 호이스팅)을 보고 학습한 내용을 토대로 말해보겠다!

일단 위의 예시는 main1이라는 함수가 add라는 서브 함수를 사용하는 경우이다. 이 때 서브 함수는 어디에 선언하는 것이 적절할까라는 물음표를 던지는 예시이다. 하나의 가정을 더 추가해보자. add 함수는 main1 함수에서만 사용된다는 점!

그렇다면 3번과 4번은 적절치 못한 사용이 된다. 일단 다른 파일에서도 사용할 일이 없기 때문에 만천하에 드러내는 것은 흠... 옳지 않은 것 같다. 다른 사람이 읽을 때는 이게 어디에서 또 사용되는지 헷갈려할 것 아닌가? 3번도 그런 의미에서 동일하다. 마치 얘가 main2, main3 함수에서도 쓰일 것 같이 헷갈리게 만든다.

따라서 1번 혹은 2번인데, 이 때는 또 상황에 따라 다르다. main1 함수를 해석하는 데에 add라는 함수가 정말 중요하다면 앞에 오는 것이 맞을 수 있다. 반면 정말 쩌리 그 자체인 서브 함수라면 마지막인 2번에 위치해서 '궁금하면 밑으로 내려서 읽어봐'라는 의도로 선언해주는 것이 적절하다. 사람은 위에서 아래로 코드를 읽으니까! 위에서는 핵심만 읽게 도와주는 것이다. 바로 이 2번 위치에 함수 선언문을 사용하는 것이 Hoisting을 이용한 예시라고 볼 수 있겠다.





참고
MDN Hoisting
자바스크립트 호이스팅, 흔한 오해 1가지
코드를 어떻게 나눠야 할까요? (feat. 호이스팅)
실행 컨텍스트
ECMA

profile
메타몽 닮음 :) email: alohajune22@gmail.com

0개의 댓글