호이스팅은 끌어올리다라는 의미로, 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(이하 VE)와 Lexical Environment(이하 LE)에 대해서 살펴보자면 다음과 같다.
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
만약 전역 공간에 선언된 값중 동일한 변수명을 가진 값을 만들어 할당할 경우 어떻게 될까?
이러한 특성상 전역변수를 많이 사용하게 되면 값을 덮어씌우게 되는 등 의도한 값과 다른 값이 나타날 수 있다.
참여중인 코어 자바스크립트 스터디에서 이번주 분량은 실행 컨텍스트였다. 처음엔 내용이 이해가 안됐었는데, 반복해서 읽다보니 어느정도 이해가 됐었다.
이중 인상깊었던 부분 하나가 바로 호이스팅은 실제로 끌어올려지지 않는다는 개념이었다.
호이스팅이라는 개념 자체는 알고있던 개념이었지만, 실제 엔진 내부에서 어떻게 동작하는지는 몰랐어서 호이스팅에 대한 개념을 다시한번 정리해보았다.
혹시라도 잘못된 내용이 있다면 댓글로 알려주심 감사하겠습니다.
좋은글 잘 읽었습니다 :)
첫번째 예시 함수의 이름이 변경되어야 할 거 같네요!