Hoisting | 호이스팅

a = 2;
var a;
console.log(a);

console.log의 결과 값은 undefined인 것 처럼 보여지지만 실제로는 2가 출력된다.

console.log(a);
var a = 2;

이것 역시 ReferenceError가 발생한다고 할 수 있지만 undefined가 출력된다.

컴파일러는 두 번 공격한다.

자바스크립트 엔진이 코드를 인터프리팅하기 전에 컴파일한다.

컴파일레이션 단계 중에는 모든 선언문을 찾아 적절한 스코프에 연결해주는 과정이 있다.

이 과정이 렉시컬 스코프의 핵심이다.

변수와 함수 선언문 모두 코드가 실제 실행되기 전에 먼저 처리된다고 보면 된다.

var a = 2를 하나의 구문처럼 보이지만 자바스크립트는 2개의 구문으로 본다.

  1. var a;

  2. a = 2;

첫째 구문은 선언문으로 컴파일레이션 단계에서 처리된다.

둘쨰 구문은 대입문으로 실행단계까지 내버려둔다.

그래서 첫 번째 코드는 다음과 같이 처리가 된다.

// 컴파일레이션 단계
var a;
// 실행 단계
a = 2;
console.log(a);
// -----------------------
// 컴파일레이션 단계
var a;
// 실행 단계
console.log(a);
a = 2;

이 과정을 비유적으로 말하면 변수와 함수 선언문은 선언된 위치에서 코드의 꼭대기로 끌어올려진다.

이렇게 선언문을 끌어올리는 동작을 호이스팅이라고 한다.

즉 선언문이 대입문보다 먼저다.

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

함수 foo의 선언문은 끌어올려졌으므로 foo를 첫째 줄에서도 호출할 수 있다.

호이스팅은 스코프별로 작동한다는 점도 중요하다.

함수 foo 안에서도 변수 a가 foo의 꼭대기로 끌어올려진다.

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

함수 선언문은 끌어올려지지만 함수 표현식은 다르다.

foo(); // TypeError
var foo = function bar() {};

변수 확인자 foo는 끌어올려져 글로벌 스코프에 붙으므로 foo 호출은 실패하지 않고 ReferenceError도 발생하지 않는다.

하지만 foo는 아직 값을 가지고 않는 undefined이다 그 때 호출을 하려고 해서 TypeError가 발생한다.

함수 표현식이 이름을 가져도 그 이름 확인자는 해당 스코프에서 찾을 수 없다는 점이다.

foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() {};

함수가 먼저다

함수와 변수 선언문은 모두 끌어올려진다. 하지만 미묘한 차이가 존재한다 먼저 함수가 끌어올려지고 그 다음에 변수가 올려진다.

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

결과값은 2가 아닌 1로 출력된다.

function foo() {
console.log(1);
}
foo();
foo = function() {
console.log(2);
};

var foo가 중복된 선언문이라서 무시가 된다. var foo는 function foo 선언문 보다 앞서 선언됐지만 함수 선언문이 일반 변수 위로 끌어올려졌다.

많은 중복 변수 선언문이 사실상 무시됐지만 중복 함수 선언문은 앞선 것들을 겹쳐 쓴다.

foo(); // 3
function foo() {
console.log(1);
}
var foo = function() {
console.log(2);
};
function foo() {
console.log(3);
}

일반 블록 안에서 보이는 함수 선언문은 보통 둘러싼 스코프로 끌어올려지지만 다음 코드 처럼 따르지 않을 수도 있다.

정리하기

var a = 2는 하나의 구문 처럼 보이지만 자바스크립트 엔진에서는 var a 와 a = 2라는 독립된 구문의로 본다.

첫 번쨰 구문은 컴파일러 단계에서 처리하고 두 번쨰는 실행 단계에서 처리한다.

이것이 의미하는 바는 스코프의 모든 선언문은 실행 전에 먼저 처리된다는 점이다.

호이스팅이라 불리는 이 과정은 변수와 함수 선언문 각각이 속한 스코프의 꼭대기로 끌어올려지는 작업이라고 생각할 수있다.

선언문 자체는 옮겨지지만 함수 표현식의 대입문으로 포함한 모든 대입문은 끌어올려지지 않는다.