호이스팅 (Hoisting)
호이스팅(Hoisting)은 변수와 함수의 선언이 해당 스코프의 최상단으로 끌어올려지는 행동을 말합니다.
호이스팅은 코드가 실제로 실행되는 순서와 다르게 동작할 수 있기 때문에 혼란을 줄 수 있습니다.
변수 호이스팅
JavaScript에서 변수는 var
, let
, const
로 선언할 수 있습니다.
이 중 var
로 선언된 변수는 호이스팅의 영향을 받습니다.
let
과 const
로 선언된 변수는 호이스팅의 영향을 받지만, TDZ(Temporal Dead Zone) 때문에 선언 전에 접근하면 에러를 발생시키고, 이로 인해 잠재적인 버그를 줄일 수 있습니다.
var 호이스팅
1
2
3
console.log(x); // undefined
var x = 5;
console.log(x); // 5
위 코드에서 console.log(x)
를 실행하기 전에 변수 x
를 선언하지 않았음에도 불구하고 undefined
가 출력됩니다.
이는 var x
선언이 코드의 최상단으로 호이스팅되었기 때문입니다.
실제 실행되는 코드는 다음과 같습니다.
1
2
3
4
var x;
console.log(x); // undefined
x = 5;
console.log(x); // 5
얼핏 보면 에러를 줄일 수 있어 좋아 보이지만, 바꿔 말하면 에러가 발생해야 하는 부분에서 에러가 발생하지 않으므로 잠재적인 버그를 만들 수 있습니다.
let과 const 호이스팅
let
과 const
로 선언된 변수도 호이스팅되지만, TDZ(Temporal Dead Zone) 라는 것을 만들었기 때문에, 선언 전에는 접근할 수 없습니다.
1
2
3
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;
console.log(y); // 10
위의 경우 let y
선언이 호이스팅되지만, 실제 값의 할당은 코드에서 해당 줄에 도달했을 때 이루어집니다. 따라서 초기화 전에 변수를 참조하려고 하면 ReferenceError
가 발생합니다.
함수 호이스팅
함수에서도 호이스팅이 발생하는데, 함수 선언(Function Declaration)과 함수 표현식(Function Expression)의 호이스팅이 다릅니다.
함수 선언
1
2
3
4
5
console.log(myFunction()); // "Hello, World!"
function myFunction() {
return "Hello, World!";
}
함수 선언은 전체 코드의 최상단으로 호이스팅되기 때문에, 함수 선언부보다 먼저 함수를 호출하더라도 문제가 발생하지 않습니다.
함수 표현식
1
2
3
4
5
console.log(myFunction()); // TypeError: myFunction is not a function
var myFunction = function() {
return "Hello, World!";
}
함수 표현식은 변수 할당과 동일하게 처리되므로, 함수 표현식이 실행되기 전에 호출하면 TypeError
가 발생합니다.
위 코드에서 var myFunction
선언은 호이스팅되지만, 함수 자체는 할당되지 않았기 때문에 myFunction
은 undefined
로 평가됩니다.
조건문과 반복문에서의 호이스팅
조건문과 반복문에서도 호이스팅이 발생할 수 있습니다.
조건문 내에서 변수 선언이 호이스팅될 때, 선언된 변수는 블록 스코프가 아닌 함수 또는 전역 스코프에서 호이스팅됩니다.
1
2
3
4
if (true) {
var x = 5;
}
console.log(x); // 5
위 코드에서 var x
선언은 조건문 내에서 이루어졌지만, 호이스팅에 의해 함수 또는 전역 스코프의 최상단으로 끌어올려집니다. 따라서 조건문 밖에서도 x
에 접근할 수 있습니다.
반면에 let
과 const
로 선언된 변수는 블록 스코프를 가지므로 조건문 밖에서는 접근할 수 없습니다.
1
2
3
4
if (true) {
let y = 10;
}
console.log(y); // ReferenceError: y is not defined
반복문에서도 변수 호이스팅이 발생합니다.
1
2
3
4
for (var i = 0; i < 3; i++) {
// 반복문 블록
}
console.log(i); // 3
반복문 내에서 함수 생성 시의 호이스팅
반복문 내에서 함수를 생성할 때, var
와 let
의 차이는 더욱 두드러집니다.
1
2
3
4
5
for (var k = 0; k < 3; k++) {
setTimeout(function() {
console.log(k); // 3, 3, 3
}, 1000);
}
이 코드는 var k
가 함수 스코프를 가지기 때문에, 반복문이 종료된 후 k
는 3으로 유지됩니다. 따라서 setTimeout
내에서 출력되는 k
는 모두 3입니다.
반면에 let
을 사용하면 각 반복마다 새로운 블록 스코프가 생성됩니다.
1
2
3
4
5
for (let l = 0; l < 3; l++) {
setTimeout(function() {
console.log(l); // 0, 1, 2
}, 1000);
}
이 경우 각 반복마다 새로운 l
이 생성되므로, setTimeout
내에서 출력되는 l
은 0, 1, 2가 됩니다.
문제점
- 초기화되지 않은 변수 접근: 변수가 호이스팅되어 undefined로 초기화되기 때문에, 변수에 값을 할당하기 전에 접근하게 되면 의도하지 않은 결과를 초래할 수 있습니다.
- 함수 표현식의 예기치 않은 동작: 함수 표현식은 변수의 호이스팅 규칙을 따르기 때문에, 함수가 정의되기 전에 호출하면 TypeError가 발생합니다.
- 변수의 재선언 및 재할당 문제: var는 변수의 재선언이 가능하기 때문에, 의도하지 않은 재할당이 발생할 수 있습니다.
- 함수와 변수 간의 이름 충돌: 함수와 변수의 이름이 동일할 때, 호이스팅으로 인해 예기치 않은 동작이 발생할 수 있습니다.
요약
var
로 선언된 변수는 호이스팅되며, 선언 전에 접근하면undefined
가 반환됩니다.let
과const
로 선언된 변수도 호이스팅되지만, TDZ(Temporal Dead Zone) 때문에 선언 전에 접근하면ReferenceError
가 발생합니다.- 함수 선언은 호이스팅되어 선언 전에 호출이 가능합니다.
- 함수 표현식은 변수 호이스팅과 동일하게 동작하여, 선언 전에 호출하면
TypeError
가 발생합니다. - 반복문 내에서도 변수 호이스팅이 발생하며,
var
와let
의 차이에 따라 동작이 달라질 수 있습니다.