컴파일러는 두 번 공격한다 #호이스팅
4. 호이스팅
4.1 컴파일러는 두 번 공격한다
한 스코프 안에서 선언된 변수는 바로 그 스코프에 속한다.
자바스크립트 프로그램은 1장의 컴파일러 이론에 따라 실행된다. 자바스크립트 엔진이 코드를 인터프리팅 하기 전에 컴파일한다. 컴파일레이션 단계 중에는 모든 선언문을 찾아 적절한 스코프에 연결해주는 과정이 있었다. 이게 바로 2장 렉시컬 스코프의 핵심이다.
변수와 함수 선언문은 모두 코드가 실제 실행되기 전에 먼저 처리된다고 보면 된다.
“var a = 2;”는 하나의 구문이라 생각할 수 있지만 자바스크립트는 두 개의 구문으로 본다.
var a; // 선언문 (컴파일레이션 단계)
a = 2; // 대입문 (실행 단계)
이 과정에서 변수와 함수 선언문은 선언된 위치에서 코드의 꼭대기로 끌어올려진다. 이렇게 선언문을 끌어올리는 동작을 호이스팅이라고 한다.(호이스팅은 스코프 별로 작동한다.) 즉, 선언문이 대입문보다 먼저다!
여기서 주의할 점은 선언문만 끌어올려지고 다른 대입문이나 실행 로직 부분은 제자리에 그대로 있기 때문에 호이스팅 코드 실행 로직 부분이 재배치된다면 유심히 살펴봐야 한다.
foo();
function foo() {
console.log(a); // undefined
var a = 2;
}
함수 foo의 선언문은 끌어올려졌으므로 foo를 첫째 줄에서도 호출할 수 있다. 호이스팅이 스코프 별로 작동한다는 점도 중요하다. 함수 foo() 내에서도 변수 a가 foo()의 꼭대기로 끌어올려진다. 즉 아래와 같이 동작한다고 볼 수 있다.
foo();
function foo() {
var a;
console.log(a); // undefined
a = 2;
}
또 기억할 것은 함수 표현식이 이름을 가져도 그 이름 변수는 해당 스코프에서 찾을 수 없다는 점이다.
foo(); // TypeError
bar(); // RefereneceError
var foo = function bar() { // bar() 접근 불가
// ...
};
이 코드에 호이스팅을 적용하면 다음과 같다.
var foo;
foo(); // TypeError
bar(); // RefereneceError
foo = function() {
var bar = ...self...
// ...
};
4.2 함수가 먼저다
함수와 변수 선언문은 모두 끌어올려지는 와중에 함수가 먼저 끌어올려지고 다음으로 변수가 올려진다.
foo(); // 1
var foo;
function foo() {
console.log(1);
}
foo = function() {
console.log(2);
};
결괏값으로 2가 아니라 1이 출력된다. 엔진은 다음과 같이 해석한다.
function foo() {
console.log(1);
}
foo(); // 1
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”라는 두 개의 독립된 구문으로 보고 첫째 구문은 컴파일러 단계에서, 둘째 구문은 실행 단계에서 처리한다.
이것이 의미하는 바는 스코프의 모든 선언문은 어디서 나타나든 실행 전에 먼저 처리된다는 점이다. ‘호이스팅'이라 불리는 이 과정은 선언문이 각각이 속한 스코프의 꼭대기로 ‘끌어올려' 지는 작업이라고 생각할 수 있다. 그 과정에서 선언문 자체는 옮겨지지만 함수 표현식의 대입문을 포함한 모든 대입문은 끌어올려 지지 않는다.
중복 선언을 조심하자. 일반 변수 선언과 함수 선언을 섞어 사용하면 안된다.
Ref
[도서] 카일 심슨, ⌜YOU DON'T KNOW JS - 타입과 문법, 스코프와 클로저⌟, 한빛미디어, 2017