JavaScript / / 2023. 12. 28. 17:18

[자바스크립트 스터디] 자바스크립트에서의 호이스팅(Hoisting)

 

저번 글에서 Execution Context(실행 컨텍스트)의 environmentRecord가 미리 식별자의 정보를 수집한다고 언급했을 것이다. 이러한 environmentRecord에 대해 좀 더 자세하게 들여다 보겠다.

environmentRecord는 해당 Lexical Environment에 있는 범위 내의 식별자를 정의한다. 매번 자바스크립트 코드가 평가될때 마다, 함수 선언, 블록 스코프 혹은 Catch와 Try구문을 발견할 때 마다 새로운 식별자가 바인딩 되어 environmentRecord에 기록된다. 

 

이러한 environmentRecord에는 3개의 하위 클래스로 이루어져 있다.

 

  • Declarative Environment Record(선언적 환경 레코드)

var, let, const, class, module, import, function 등의 해당 스코프 내의 식별자들의 바인딩 - 여기서 바인딩이란 식별자와 그 값을 묶는 다는 것 - 을 관리한다. 선언적 환경 레코드는 두가지로 다시 나뉜다.

 

하나는 Function Environment Record(함수 환경 레코드)이다. 말 그대로 함수랑 관계되는 것으로, 함수의 상위 스코프를 결정하며 - 이 때 함수를 호출한 위치가 아닌 함수를 어디에서 정의했는지에 따라 달라진다. 또한 이때 this가 바인딩 되며, 이 this는 함수의 호출 방식에 따라 결정된다.  

 

나머지 하나는 Module Environment Record(모듈 환경 레코드)이다. 모듈의 외부 스코프를 나타낼 때 사용한다. 

 

  • Object Environment Record(객체 환경 레코드)

with문에 대해 실행 컨텍스트를 만들 때, 여기다 저장한다. 현재의 스코프의 변수들을 대상으로, Object Environment Record는 객체와 관련된 프로퍼티나 메소드의 접근을 제공한다. 추가적으로 Object Environment Record는 두 가지 필드를 가진다. [[BindingObject]], 환경 레코드에 바인딩된 객체, 그리고 [[IsWithEnvironment]]로 with문에 의해 생성됐는지를 나타내는 값이다. 

  • Global Environment Record(전역 환경 레코드)

빌트인 객체들, 전역 객체의 프로퍼티 그리고 전역 스코프에서의 선언에 관한 바인딩을 관리한다. 모든 자바스크립트 코드들이 공유하는 최상위 스코프를 나타낸다. 

 

이제부터 호이스팅(Hoisting)을 본격적으로 알아보자. 하나 알아둘 것은 호이스팅은 그럴듯 하게 보이는 현상이라는 것이다. 실제로 변수가 위로 끌려 올라가지는 것이 아니다. 그렇게 보이는 듯한 현상이다. 다음 예제를 보자.

var a = "Hello";

function outer() {
  console.log(a);
  
  function inner() {
    console.log(a);
  }
  var a = "bye";
  inner();
}
outer();
console.log(a);

 

위의 코드를 실행하면 어떻게 나올까? 밑의 결과가 나온다. 

왜 이렇게 나올까? 한번 하나씩 살펴보겠다. 일단 하나 알아둘 것이 있다. 바로 environmetRecord가 식별자를 수집하지만, 이는 함수 혹은 변수가 선언될 당시의 정보를 가져온다. 즉 호출 - 실제로 코드에서 실행하려고 실행문을 쓸 때 - 가 아닌, 선언 했을 때의 정보이다. 

 

그렇다면 순서를 알아보자.

 

  1. 먼저 코드 전역에 걸쳐서 정보를 수집한다. 이때 코드 전역에 있는 함수 혹은 변수는 a와 outer()이다. 이때 a의 값은 할당되지 않는다.
  2. 그렇게 정보 수집이 끝났으면 var a = "hello"가 담긴다. 그 다음에는 outer()가 실행된다.
  3. outer 함수가 실행되면서 outer 컨텍스트가 생성된다. 이때 outer 내부에 - environment Record 에 쌓인다 - inner() 함수와 a가 쌓인다.
  4. 그다음 a를 찾는다. 이때 a가 hoisting에 의해 environment Record에 미리 기록되어 있으므로 undefined를 출력한다.
  5. 그 후 a에 "bye"가 할당된다.
  6. 그 다음 inner()가 실행된다. 이 때 a를 함수 내부에서 찾을 수 없음으로 다음 시간에 언급할 스코프체인 - 간단하게 말해 콜스택 상에서 상위 컨텍스트를 찾는 체인같은 역할을 한다 - 을 따라 변수를 찾는다. outer안에 있는 a를 찾았다. inner()가 호출되기 전에 이미 "bye"가 할당되어 있기 때문에 "bye"를 출력한다.
  7. 그리고 outer()가 끝난 뒤에 console.log(a)를 실행한다. 이 때  "bye"가 아닌 "Hello"가 출력되는 이유는 스코프 범위 상에서 outer()안과 밖의 a는 서로 다른 변수이기 때문이다. 즉 서로 다른 환경이다. 그렇기에 a에는 "Hello"가 할당되어서 "Hello"가 출력된다.

3번에서 만약 변수가 재선언 되지 않은 상태에서 할당만 이루어졌다면 어찌 되었을까?

 

 

위의 상태처럼 "Hello"와 "bye", "bye"가 출력된다. 왜 그런 것일까?

 

  1. 먼저 코드 전역에 걸쳐서 정보를 수집한다. 이때 코드 전역에 있는 함수 혹은 변수는 a와 outer()이다. 이때 a의 값은 할당되지 않는다.
  2. 그렇게 정보 수집이 끝났으면 var a = "hello"가 담긴다. 그 다음에는 outer()가 실행된다.
  3. outer 함수가 실행되면서 outer 컨텍스트가 생성된다. 이때 outer 내부에 - environment Record 에 쌓인다 - inner() 함수만 쌓인다. 왜냐하면 변수가 선언되어 있지 않은 환경이기 때문이다.
  4. 그다음 a를 찾는다. 이때 a가 내부에 없으므로 스코프 체인을 따라서 상위 스코프 변수를 찾아간다. 이 때 outer() 바깥에 있는 a를 찾는다. 이미 "Hello"가 할당되어 있으므로 "Hello"를 출력한다.
  5. 그다음 a에다가 "bye"를 할당한다.
  6. 그 다음 inner()가 실행된다. 이 때 a를 함수 내부에서 찾을 수 없음으로 다음 시간에 언급할 스코프체인 - 간단하게 말해 콜스택 상에서 상위 컨텍스트를 찾는 체인같은 역할을 한다 - 을 따라 변수를 찾는다. outer밖에 있는 a를 찾았다. inner()가 호출되면서 이미 "bye"를 할당했기 때문에 "bye"를 출력한다. 
  7. 그리고 console.log(a)에서도 "bye"를 출력하는데, 이는 outer()가 호출되면서 스코프 체인으로 상위 범위를 찾아 a를 가져와서 "bye"를 할당했기 때문이다.

이렇듯 호이스팅은 변수의 선언과 그 당시 함수가 어디에 어떻게 선언되어 있는지에 따라 크게 갈린다.  이번 글에서는 environment Record를 공부하면서 호이스팅에 대해 알아봤다. 그렇다면 나머지 한가지인 outerEnvironmentReference는 무엇일까? 그것은 Closure하고도 관계가 있기에 다음 시간에 알아보려고 한다. 

 

728x90
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유