Hoisting

JavaScript

Hoisting?

Execution Context(실행 컨텍스트)에서 다뤘듯이, environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다. 자바스크립트 엔진은 컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 순서대로 수집한다.

변수 정보를 수집하는 과정을 모두 마쳤더라도 아직 실행 컨텍스트가 관여할 코드들은 실행되기 전의 상태이다. 코드가 실행되기 전임에도 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명들을 모두 알고 있게 된 셈이다.

Hoisting(호이스팅)이란 ‘끌어올리다’의 ‘hoist’에 ‘ing’을 붙여 만든 동명사로, 변수 정보를 수집하는 과정을 이해하기 쉬운 방법으로 대체한 가상의 개념이다. 자바스크립트 엔진이 실제로 끌어올리지는 않지만 편의상 끌어올린 것으로 간주하자는 것이다.

매개변수와 변수에 대한 호이스팅

function a(x){ // 수집 대상 1(매개변수)
  console.log(x); // (1)
  var x; // 수집 대상 2(변수 선언)
  console.log(x); // (2)
  var x = 2; // 수집 대상 3(변수 선언)
  console.log(x); // (3)
}
a(1);

예시 1 : 원본 코드

추측 : 1, undefined, 2

(1)에는 함수 호출 시 전달한 1이 출력되고, (2)는 선언된 변수 x에 할당한 값이 없으므로 undefined가 출력되고, (3)에서는 2가 출력되기 때문

위의 코드에서 인자들과 함께 함수를 호출한 경우의 동작을 살펴보면, arguments에 전달된 인자를 담는 것을 제외하면 다음의 예시 2처럼 코드 내부에서 변수를 선언한 것과 다른 점이 없다. 특히 LexicalEnvironment입장에서는 완전히 같다. 그러니까, 인자를 함수 내부의 다른 코드보다 먼저 선언 및 할당이 이루어진 것으로 간주할 수 있다.

function a(){
  var x = 1; // 수집 대상 1(매개변수 선언)
  console.log(x); // (1)
  var x;  // 수집 대상 2(변수 선언)
  console.log(x); // (2)
  var x = 2; // 수집 대상 3(변수 선언)
  console.log(x); // (3)
}
a();

예시 2 : 매개변수를 변수 선언/할당과 같다고 간주해서 변환한 상태

environmentRecord는 현재 실행될 컨텍스트의 대상 코드 내에 어떤 식별자들이 있는지에만 관심이 있고 각 식별자에 어떤 값이 할당될 것인지에는 관심이 없다. 따라서 변수를 호이스팅할 때 변수명만 끌어올리고 할당 과정은 원래 자리에 그대로 남겨둔다.

function a(){
  var x;  // 수집 대상 1의 변수 선언 부분
  var x;  // 수집 대상 2의 변수 선언 부분
  var x;  // 수집 대상 3의 변수 선언 부분

  x = 1; // 수집 대상 1의 할당 부분
  console.log(x); // (1)
  console.log(x); // (2)
  x = 2; // 수집 대상 3의 할당 부분
  console.log(x); // (3)
}
a();

예시 3 : 매개변수를 변수 선언/할당과 같다고 간주해서 변환한 상태

  1. 2번째 줄 : 변수 x를 선언한다. 메모리에서 저장할 공간을 미리 확보하고 , 확보한 공간의 주소값을 변수 x에 연결해둔다.
  2. 3, 4번째 줄 : 다시 변수 x를 선언한다. 이미 선언된 변수 x가 있으므로 무시한다.
  3. 6번째 줄 : x1을 할당하려고 한다. 우선 숫자 1을 별도의 메모리에 담고, x와 연결된 메모리 공간에 1을 가리키는 주소값을 입력한다.
  4. 7, 8번째 줄 : 각 x를 출력한다. 모두 1이 출력된다.
  5. 9번째 줄 : x2를 할당하려고 한다. 숫자 2를 별도의 메모리에 담고, 그 주소값을 든 채로 x와 연결된 메모리 공간으로 간다. 여기에는 1을 가리키는 주소값이 들어있었는데, 이걸 2의 주소값으로 대치한다. 이제 x2를 가리키게 된다.
  6. x를 출력하라고 하니 (3)에서는 2가 출력된다.
  7. 함수 내부의 모든 코드가 실행되었으므로 실행 컨텍스트가 콜 스택에서 제거된다.

결과 : 1, 1, 2

함수 선언에 대한 호이스팅

function a(){ 
  console.log(b); // (1)
  var b = 'bbb'; // 수집 대상 1(변수 선언)
  console.log(b); // (2)
  function b() {} // 수집 대상 2(함수 선언)
  console.log(b); // (3)
}
a();

예시 1 : 원본 코드

추측 : undefined, ‘bbb’, function b

(1)에는 b의 값이 없으니 에러가 나거나 undefined가 나올 것이다.

위의 코드에서 a함수를 실행하는 순간 a함수의 실행 컨텍스트가 생성된다. 변수명과 함수 선언의 정보를 위로 끌어올린다. 변수는 선언부와 할당부를 나누어 선언부만 끌어올리는 반면 선언은 함수 전체를 끌어올린다.

function a(){
  var b; // 수집 대상 1(변수는 선언부만 끌어올린다)
  var b = function b() {} // 수집 대상 2 (함수 선언은 전체를 끌어올린다. 호이스팅이 끝난 상태에서의 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있다.)

  console.log(b); // (1)
  b = 'bbb'; // 변수의 할당부는 원래 자리에 남겨둔다
  console.log(b); // (2)
  console.log(b); // (3)
}
a();

예시 2 : 호이스팅을 마친 상태

  1. 2번째 줄 : 변수 b를 선언한다. 메모리에서 저장할 공간을 미리 확보하고, 확보한 공간의 주소값을 b에 연결해둔다.
  2. 3번째 줄 : 다시 변수 b를 선언하고 함수 b를 선언된 변수 b에 할당하려고 한다. 이미 선언된 변수 b가 있으므로 선언 과정이 무시된다. 함수는 별도의 메모리에 담길 것이고, 그 함수가 저장된 주소값을 b와 연결된 공간에 저장한다. 이제 변수 b는 함수를 가리키게 된다.
  3. 5번째 줄 : b에 할당된 함수 b를 출력한다. (1)
  4. 6번째 줄 : 변수 b'bbb' 를 할당하려고 한다. b와 연결된 메모리 공간에 문자열 'bbb'가 담긴 주소값으로 덮어쓴다.
  5. 7, 8번째 줄 : (2)(3) 모두 'bbb' 가 출력된다.
  6. 함수 내부의 모든 코드가 실행되었으므로 실행 컨텍스트가 콜 스택에서 제거된다.

결과 : function b, ‘bbb’, ‘bbb’


출처
코어 자바스크립트 (정재남)