호이스팅이란 선언문을 스코프의 최상단으로 끌어올리는 것 처럼 동작하는 특징을 의미합니다.
호이스팅의 사전적 의미는 끌어 올리기 입니다. 그러면, 자바스크립트에서 호이스팅은 무엇을 끌어올리냐는 것인데, 선언문을 끌어올립니다.
선언문에는 var a
와 같은 변수 선언 뿐만 아니라, 함수, 클래스 또한 포함됩니다.
이러한 코드는
console.log(a) // undefined
var a
b() // hello
function b() {
console.log('hello')
}
다음과 같이 작동합니다.
var a // 변수 선언문이 스코프의 최상단으로 올라감
function b() {
console.log('hello')
}
console.log(a)
b() // hello
호이스팅을 이해하기 위해 참조에러, 스코프, 선언문을 하나씩 알아봅시다.
ReferenceError(참조에러)
는 선언하지 않는 변수에 접근할때 발생하는 에러입니다.
a is not defined
를 통해 선언되지 않은 변수 a에 접근했기 때문에 발생했음을 알 수 있습니다.
console.log(a)
하지만, 호이스팅이 일어나면 ReferenceError에러가 발생하지 않았습니다.
console.log(a) // undefined
var a
스코프는 유효범위를 의미합니다.
대부분의 프로그래밍 언어에서 선언문은 블록 레벨을 지원합니다. 하지만, 자바스크립트의 var는 함수레벨 스코프를 지원합니다.
함수 내에서 선연된 변수는 함수 내부에서만 유효하며, 함수 외부에서는 참조할 수 없습니다.
즉, 함수 내부에 작성하지 않은 모든 변수는 전역변수가 됩니다.
함수 내부에 작성하지 않는 경우 try/catch, if, for, while 등
코드 블록 내부에 선언된 변수는 외부에서 참조할 수 없습니다. 블록 내부에서 선언한 변수는 지역 변수입니다.
class Main {
public static void main(String[] args) {
if(true) {
int a = 1; // 블록 스코프
System.out.println(a);
}
System.out.println(a); // cannnot find symbol
}
}
if(true) {
var a = 1 // 함수내부에 선언되지 않았기 때문에 전역변수
console.log(a)
}
console.log(a)
다만, ES6에 추가된 let, const 는 블록레벨 스코프
를 지원합니다.
if(true) {
let a = 1
console.log(a)
}
console.log(a)
다음의 예시를 본다면 var는 호이스팅이 되었고, let, const는 안된것 처럼 보이지만, 사실 조금 다릅니다.
첫번재 코드에서 let은 블록레벨 스코프를 가지고, 블록에서 선언된 a가 없기 때문에 1을 출력했습니다.
다만, 두번재 코드에서 상위 스코프에 b가 있음에도 참조에러가 발생했습니다.
이는 let에서도 호이스팅이 발생함을 알 수 있습니다.
이 차이를 알기 위해 TDZ(Temporary Dead Zone)을 알아봅시다.
자바스크립트의 변수는 다음 3가지 과정에 따라 생성됩니다.
1. 선언(Declaration): 스코프에 변수객체를 등록합니다.
2. 초기화(Initialization): 변수를 위한 공간을 메모리에 할당합니다. undefined로 초기화합니다.
3. 할당(Assignment): 변수 객체에 값을 할당합니다.
var의 경우 선언문에서 한번에 초기화까지 이루어집니다.
하지만, let은 선언만 되었다가, 선언문을 만나고 비로서 초기화됩니다.
호이스팅 되고 선언문을 만나기 까지 일시적으로 접근할 수 없는 상태가 되는데 이를 TDZ에 들어갔다고 표현합니다.
const는 한번에 할당까지 이루어저야합니다. 다음과 같이 선언만 하면 에러가 발생합니다.
const name
// Uncaught SyntaxError: Missing initializer in const declaration
var 는 선언부에서 undefined까지 초기화가 완료되지만, let, const는 함수 선언부를 만날때 까지 TDZ에 들어있는 상태이기 때문에
TDZ에 들어있는 값을 참조하려고 하면
undefined가 출력되는 것이 아니라
빨갛게
ReferenceError (is not defined) 참조에러를 발생시킵니다.
자바스크립트에 함수를 만드는 방법은 3가지입니다.
function sumValue(a, b) {
return a + b
}
const sumValue = (a, b) => {
return a + b
}
const sumValue = new Function('a', 'b', 'return a+b')
이 중 1번 함수 선언식만 호이스팅이 됩니다.
함수가 최상단으로 끌어올려진 것처럼 동작해서 첫줄에 에러 없이 3을 리턴합니다.
sumValue(1, 2) // 3
function sumValue(a, b) {
return a + b
}
다음 순서로 진행됩니다.
1. 변수 선언
2. 함수 선언
3. 변수 할당
변수 선언부를 진행하고, 함수 선언부가 진행되기 때문에
아래 두 결과 모두 함수 선언식이 출력되었습니다.
변수할당이 최종적으로 진행되기 때문에 할당된 값이 출력되며
함수 표현식 또한 함수 결과가 나옵니다.
사실, 요즘은 호이스팅 문제를 거의 만나지 않습니다.
호이스팅은 자바스크립트 인터프리터의 동작방식으로, 전혀 장점이라고 볼 부분은 없는 것 같습니다.
그리고, 옛날처럼 파일 하나에 js 스크립트 다 때려넣지 않기 때문에 드물기도 합니다.
사람은 위에서 아래로 읽는다.
자바스크립트 인터프리터 처럼 함수 선언부를 위로 올리고 왔다가 갔다 읽는 사람은 거의 없다.
여기서 사람과, 인터프리터 사이에 읽는 순서의 차이가 발생한다.
사람이 읽는 것처럼 코드 작성하는게 조금이나마 낫겠지...
개발자는 내가 작성한 코드가 어떻게 작동할지 이해하고 있어야한다.
호이스팅 때문에 읽는 순서와 실행 순서에 차이가 발생하면, 코드가 어떻게 작동할지 예측하는데 어려움이 생깁니다.
호이스팅은 선언부가 실행 컨텍스트 최상단으로 올라간것처럼 동작하는 것을 의미합니다.
var, let, const 모두 호이스팅이 일어납니다.
다만, let, const 를 사용하면 undefined로 초기화된것이 아니라 is not defined
참조 에러를 발생시킵니다.
변수 선언시 let, const 사용을 권장합니다.
함수를 만드는 3가지 방법 중 함수 선언식만 호이스팅이 일어납니다.
const 예약어를 사용하는 함수 표현식을 권장합니다.
변수, 함수 호이스팅 순서 - 변수 선언 -> 함수 선언 -> 변수 할당