호이스팅은 실제로 끌어올려지지 않는다

짱유경·2021년 11월 26일
5

JavaScript

목록 보기
3/3
post-thumbnail

🔌 호이스팅, hoisting

호이스팅은 끌어올리다라는 의미로, hoist에 ing을 붙여 만든 동명사이다.

function a(x) {
	console.log(x);
  	var x;
  	console.log(x);
  	var x = 2;
  	console.log(x);
}
a(1)

위 함수의 실행 결과는 어떻게 나올까? 흐름대로 읽어나가면 1 / undefined / 2 가 출력될 것 같지만, 실제로 출력되는 값은 1 / 1 / 2 이다. 이는 변수의 선언이 끌어올려짐으로써 나타나는 결과이다. 이러한 현상을 호이스팅이라고 말한다.

단어 그대로의 뜻으로 호이스팅은 실제로 변수가 "끌어올려진다"고 생각하기 쉬운데, 실제로 끌어올려지진 않고 "끌어올린 것으로 간주한다."가 정확하다.
그럼 왜 실제로 끌어올려지지 않고, 끌어올린 것으로 간주해서 이해하는 것일까? 이 부분을 이해하기 위해서는 실행 컨텍스트에 대한 이해가 필요하다.

📘 실행 컨텍스트

실행 컨텍스트는 실행할 코드에 대한 환경 정보를 모아놓은 객체이다.
실행 컨텍스트 내부에는 다음과 같은 구성요소를 갖고있다.

  • Variable Environment
  • Lexical Environment
  • ThisBinding

여기서 Variable Environment(이하 VE)와 Lexical Environment(이하 LE)에 대해서 살펴보자면 다음과 같다.

  1. VE와 LE는 동일한 구성이다.
  2. 단, VE는 최초의 스냅샷을 유지한다. 즉, 변경사항을 실시간으로 반영하지 않는다.
  3. LE는 변경사항을 실시간으로 반영한다.

environmentRecord와 호이스팅

VE와 LE에는 environmentRecord와 outerEnvironmentRecord라는 구성요소를 갖고있다. 호이스팅에 대한 개념은 environmentRecord와 관련있으므로 이에 대해 내용을 적어보자면, environmentRecord는 현재 컨텍스트와 관련된 코드의 식별자의 정보를 저장한다.

🔴 식별자는 데이터를 식별할 수 있는 이름, 즉 변수명을 뜻한다.

여기서 저장되는 식별자 정보들은, 함수에 지정된 매개변수 식별자 / 선언된 함수가 있을시 그 함수 자체 / var로 선언된 변수의 식별자 등이 식별자에 해당된다. environmentRecord는 컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 식별자에 대한 정보를 순서대로 수집한다.
그러면, 코드가 실행되기 전임에도 불구하고 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명을 모두 알고있게 되는 것이다. 그렇다면 엔진의 실제 동작 방식 대신, 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다라고 생각해도 문제가 없을 것이다.

즉, 호이스팅은 변수 정보를 수집하는 과정을 더욱 이해하기 쉬운 방법으로 대체한 가상의 개념이다. 자바스크립트 엔진이 실제로 값을 끌어올리지는 않지만 편의상 끌어올린 것으로 간주하는 것이다.

호이스팅은 선언만을 끌어올린다.

위에서 언급한 코드의 동작 방식은 아래처럼 생각할 수 있다.

function a(x) {
  var x; // 인자로 받은 값
  var x;
  var x;

  x = 1;
  console.log(x);
  console.log(x);

  x = 2;
  console.log(x);
}
a(1);

여기서 보면 알 수 있는 현상이, 호이스팅은 변수의 선언만을 끌어올리고 있다. 왜 할당은 끌어올리지 않는것인가?
이는 위에서 얘기한 environmentRecord의 특성에 기인한 것인데, environmentRecord는 식별자의 정보를 저장한다고 했었다. 즉, environmentRecord는 식별자들에 어떠한 값이 저장되는지는 관심을 갖지 않고, 컨텍스트 내부에 어떠한 식별자들이 있는지, 식별자들에 대한 정보만을 수집하는 것이다.
따라서 변수를 호이스팅할 때 변수명만 끌어올리고, 할당 과정은 원래 자리에 그대로 남겨둔다. 매개변수의 경우도 마찬가지이다.

이는 함수 표현식에서도 동일하게 작동된다.

console.log(foo());
var foo = function () {
  console.log("안녕하세요");
};

// TypeError: foo is not a function

앞서 말한것 처럼 호이스팅을 하면, 변수의 선언만을 끌어올린다고 했었다. 그러면 실제로는 다음과 같은 방식처럼 작동하게 된다.

var foo;
console.log(foo());
foo = function () {
  console.log("안녕하세요");
};

foo라는 변수를 선언하는 과정을 미리 끌어올리고, 다음에 console.log에서 foo를 호출하게 된다. 이때 foo는 미리 끌어올려지게 되지만 실제 할당하는 값은 끌어올리지 않게 되므로, 함수가 아닌 값을 함수로 호출하게 됨으로써 TypeError가 발생하게 된다.

⚠ 단, 함수 선언식은 통째로 끌어올려지게 된다. 함수도 하나의 값으로 취급할 수 있다는것이 이런식이다.

console.log(foo());
function foo() {
  console.log("안녕하세요");
}
// 안녕하세요
// undefined

중복된 값

만약 전역 공간에 선언된 값중 동일한 변수명을 가진 값을 만들어 할당할 경우 어떻게 될까?

  1. 전역 컨텍스트가 활성화될 때 전역공간에 선언된 함수들이 모두 가장 위로 끌어올려진다.
  2. 동일한 변수명에 서로 다른 값을 할당할 경우, 나중에 할당한 값이 먼저 할당한 값을 덮어씌운다.
  3. 따라서 코드를 실행하는 중에 실제로 호출되는 함수는 오직 마지막에 할당한 함수, 즉 맨 마지막에 선언된 함수뿐이다.

이러한 특성상 전역변수를 많이 사용하게 되면 값을 덮어씌우게 되는 등 의도한 값과 다른 값이 나타날 수 있다.

📝 정리

  1. 호이스팅은 실제로 값이 끌어올려지는게 아니라 LE 내부의 environmentRecord에서 속한 환경의 변수명을 모두 알고 있으므로 나타나는 현상을 이해하기 쉽게 만들어낸 가상의 동작방식이다.
  2. 호이스팅은 선언만을 끌어올린다. 이는 environmentRecord가 식별자만을 저장하기 때문에 나타나는 결과이다.
  3. 선언만을 끌어올리는 호이스팅의 특성상 함수 표현식이 선언되기 전에 호출을 할 경우 TypeError가 나타난다.
  4. 동일한 식별자를 가진 값이 있으면 가장 마지막에 할당한 값이 먼저 할당한 값을 덮어씌운다.

✨ 마치며

참여중인 코어 자바스크립트 스터디에서 이번주 분량은 실행 컨텍스트였다. 처음엔 내용이 이해가 안됐었는데, 반복해서 읽다보니 어느정도 이해가 됐었다.
이중 인상깊었던 부분 하나가 바로 호이스팅은 실제로 끌어올려지지 않는다는 개념이었다.
호이스팅이라는 개념 자체는 알고있던 개념이었지만, 실제 엔진 내부에서 어떻게 동작하는지는 몰랐어서 호이스팅에 대한 개념을 다시한번 정리해보았다.

📎 출처

코어 자바스크립트 (저자: 정재남)


혹시라도 잘못된 내용이 있다면 댓글로 알려주심 감사하겠습니다.

2개의 댓글

comment-user-thumbnail
2022년 1월 16일

좋은글 잘 읽었습니다 :)

첫번째 예시 함수의 이름이 변경되어야 할 거 같네요!

1개의 답글