JavaScript에서 호이스팅(hoisting)이란, 인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미한다. var로 선언한 변수의 경우 호이스팅 시 undefined로 변수를 초기화한다. 반면 let과 const로 선언한 변수의 경우 호이스팅 시 변수를 초기화하지 않는다. (그래서 let, const 사용시 hoisting이 안 된 것 처럼 보이기도 하는 것)
호이스팅을 설명할 땐 주로 "변수의 선언과 초기화를 분리한 후, 선언만 코드의 최상단으로 옮기는" 것으로 말한다. 따라서 변수를 정의하는 코드보다 사용하는 코드가 앞서 등장할 수 있다. 다만 선언과 초기화를 함께 수행하는 경우, 선언 코드까지 실행해야 변수가 초기화된 상태가 된다.
JavaScript는 함수의 코드를 실행하기 전에 함수 선언에 대한 메모리부터 할당한다. 덕분에 함수를 호출하는 코드를 함수 선언보다 앞서 배치할 수 있다.
function catName(name) {
console.log("제 고양이의 이름은 " + name + "입니다");
}
catName("호랑이");
/*
결과: "제 고양이의 이름은 호랑이입니다"
*/
catName("클로이");
function catName(name) {
console.log("제 고양이의 이름은 " + name + "입니다");
}
/*
결과: "제 고양이의 이름은 클로이입니다"
*/
함수 호출이 함수 자체보다 앞서 존재하지만, 그럼에도 불구하고 이 코드 역시 동작하게 되는 것을 알 수 있다. 이것이 JavaScript에서 실행 콘텍스트가 작동하는 방식이다.
호이스팅은 다른 자료형과 변수에도 잘 작동한다. 변수를 선언하기 전에 먼저 초기화하고 사용할 수 있다는 것이다.
JavaScript는 초기화를 제외한 선언만을 호이스팅하게 된다. 변수를 먼저 사용하고 그 후에 선언 및 초기화가 나타나면, 사용하는 시점의 변수는 기본 초기화 상태(var 선언 시 undefined, 그 외에는 초기화하지 않음)이다. (즉 var x = 6; 이런식으로 작성한 것이 호이스팅이 일어나면 x는 undefined 상태로 나온다는 것)
console.log(num); // 호이스팅한 var 선언으로 인해 undefined 출력
var num; // 선언
num = 6; // 초기화
반면, 다음 예제는 선언 없이 초기화만 존재한다. 따라서 호이스팅도 없고, 변수를 읽으려는 시도에서는 ReferenceError 예외가 발생하게된다.
console.log(num); // ReferenceError
num = 6; // 초기화
// 예제 1
// y만 호이스팅 대상
x = 1; // x 초기화. x를 선언하지 않은 경우 선언. 그러나 명령문에 var가 없으므로 호이스팅이 발생하지 않음
console.log(x + " " + y); // '1 undefined'
// JavaScript는 선언만 호이스팅하므로, 윗줄의 y는 undefined
var y = 2; // y를 선언하고 초기화
// 예제 2
// 호이스팅은 없지만, 변수 초기화는 (아직 하지 않은 경우) 변수 선언까지 병행하므로 변수를 사용할 수 있음
a = "크랜"; // a 초기화
b = "베리"; // b 초기화
console.log(a + "" + b); // '크랜베리'
let과 const로 선언한 변수도 호이스팅 대상이지만, var와 달리 호이스팅 시 undefined로 변수를 초기화하지는 않는다. 따라서 변수의 초기화를 수행하기 전에 읽는 코드가 먼저 나타나면 오류가 발생하게 된다.
const 변수 선언부터 시작해보자. 변수를 선언하고 초기화하면 변수에 접근할 수 있다. 예상한대로 동작한다.
const white = '#FFFFFF';
white; // => '#FFFFFF'
이번에는 선언 전에 white 변수에 접근해보도록 하겠다.
white; // throws `ReferenceError`
const white = '#FFFFFF';
white
const white = '#FFFFFF' 구문 전 줄까지, white 변수는 TDZ에 있다.
TDZ에 있는 white 변수에 접근하게 되면 , ReferenceError: Cannot access 'white' before initialization 자바스크립트 에러가 발생한다.
TDZ의 영향을 받는 구문들을 살펴보자.
이전에 보았듯이, const 변수는 선언 및 초기화 전 줄까지 TDZ에 있다.
// Does not work!
pi; // throws `ReferenceError`
const pi = 3.14;
const 변수는 선언한 후에 사용해야 한다.
const pi = 3.14;
// Works!
pi; // => 3.14
let도 선언 전 줄까지 TDZ의 영향을 받는다.
// Does not work!
count; // throws `ReferenceError`
let count;
count = 10;
다시, let 변수도 선언 이후에 사용해야 한다.
let count;
// Works!
count; // => undefined
count = 10;
// Works!
count; // => 10
머리말 부분에서 보았듯이, 선언 전에는 class를 사용할 수 없다.
// Does not work!
const myNissan = new Car('red'); // throws `ReferenceError`
class Car {
constructor(color) {
this.color = color;
}
}
이 예제가 동작하려면, 클래스를 선언한 후에 사용하도록 수정한다.
class Car {
constructor(color) {
this.color = color;
}
}
// Works!
const myNissan = new Car('red');
myNissan.color; // => 'red'
위에서 설명한 것들과 반대로 var, function 선언은 TDZ에 영향을 받지 않는다. 이것들은 현재 스코프에서 호이스팅 된다.
var 변수는 선언하기 전에 접근하면, undefined를 얻게 된다.
// Works, but don't do this!
value; // => undefined
var value;
그러나 함수는 선언된 위치와 상관없이 동일하게 호출된다.
// Works!
greet('World'); // => 'Hello, World!'
function greet(who) {
return `Hello, ${who}!`;
}
// Works!
greet('Earth'); // => 'Hello, Earth!'
종종 함수 구현보다 호출에 더 관심이 있기 때문에 함수 선언 전에 호출하게 된다. 함수 선언 전에 호출해도 에러가 발생하지 않는 이유는 호이스팅 때문이다.