JavaScript는 다른 언어에 비해 상대적으로 제약이 적어 자유롭게 느껴지는 구석이 있다.
특히 호이스팅이라는 독특한 개념 덕분에 실수한 코드가 에러 없이 실행되는 경우가 종종 있다.
얼핏 편리해 보이지만, 호이스팅은 문법적 편의를 위해 만들어진 게 아니다.
JavaScript 엔진이 코드를 2단계로 처리하는 과정에서 자연스럽게 발생하는 현상이다.
Java에서는 경험할 수 없는 JavaScript만의 재미있는 특징이기에 정리해본다.
호이스팅이란?
Hoist의 사전적 의미
Hoist는 영어로 "끌어올리다", "들어올리다"라는 뜻을 가진 동사다. 건설 현장에서 크레인으로 자재를 위로 끌어올리거나, 깃발을 게양대 위로 올리는 것을 의미한다.
🏗️ hoist = 끌어올리다, 들어올리다
예: hoist a flag (깃발을 올리다)
hoist cargo (화물을 끌어올리다)
JavaScript의 호이스팅
호이스팅(Hoisting)은 JavaScript에서 변수나 함수 선언이 코드 실행 전에 해당 스코프의 최상단으로 "끌어올려지는" 것처럼 동작하는 현상을 말한다.
실제로 코드가 물리적으로 이동하는 것은 아니지만, JavaScript 엔진이 코드를 실행하기 전에 먼저 선언을 처리하기 때문에 마치 선언이 맨 위로 올라간 것처럼 보인다.
console.log(myName); // undefined
var myName = "철수";
console.log(myName); // "철수"
위 코드를 보면 첫 번째 console.log에서 에러가 나지 않고 undefined가 출력된다. 왜 그럴까? 바로 호이스팅 때문이다!
JavaScript 엔진은 위 코드를 다음과 같이 해석한다:
var myName; // 선언이 호이스팅됨 (맨 위로 끌어올려짐)
console.log(myName); // undefined (선언은 됐지만 할당은 안 됨)
myName = "철수"; // 할당
console.log(myName); // "철수"
var 변수의 호이스팅
var로 선언한 변수는 선언부만 호이스팅되고, 할당은 원래 위치에서 이루어진다.
function example() {
console.log(x); // undefined
var x = 10;
console.log(x); // 10
}
// 실제로는 이렇게 동작
function example() {
var x; // 선언이 맨 위로
console.log(x); // undefined
x = 10; // 할당은 원래 위치
console.log(x); // 10
}
var의 함수 스코프
function test() {
if (true) {
var message = "안녕하세요";
}
console.log(message); // "안녕하세요" (에러 안 남!)
}
// 호이스팅 후
function test() {
var message; // 함수 최상단으로 호이스팅
if (true) {
message = "안녕하세요";
}
console.log(message); // "안녕하세요"
}
let과 const의 호이스팅
let과 const는 호이스팅이 안되는 것처럼 보이지만, 실제로는 호이스팅이 된다. 다만 동작 방식이 다르다.
console.log(myVar); // undefined
var myVar = 1;
console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization
let myLet = 2;
TDZ (Temporal Dead Zone) - 일시적 사각지대
TDZ란 무엇인가?
TDZ(Temporal Dead Zone, 일시적 사각지대)는 let과 const로 선언한 변수가 호이스팅은 되었지만, 초기화되지 않아서 접근할 수 없는 구간을 말한다.
호이스팅의 증명
만약 let이 호이스팅되지 않는다면, 다음 코드는 외부 스코프의 x를 참조해야 한다:
let x = "외부 x";
function test() {
// 만약 let이 호이스팅 안 된다면 "외부 x"가 출력되어야 함
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = "내부 x"; // 이 선언이 호이스팅되어 함수 시작부터 x가 존재함 (단, TDZ 상태)
}
test();
위 코드에서 에러가 발생하는 이유는:
- let x가 호이스팅되어 함수 스코프에 x가 존재한다는 것을 JavaScript 엔진이 알고 있음
- 하지만 아직 초기화되지 않았기 때문에 (TDZ) 접근할 수 없음
- 만약 호이스팅이 안 됐다면, 외부의 "외부 x"를 출력했을 것임
var vs let/const 호이스팅 비교
// === var의 경우 ===
console.log(a); // undefined
var a = 1;
// 실제 동작 (호이스팅 후)
var a = undefined; // 선언 + undefined로 초기화
console.log(a); // undefined
a = 1; // 할당
// === let의 경우 ===
console.log(b); // ReferenceError
let b = 2;
// 실제 동작 (호이스팅 후)
// let b; ← 선언은 호이스팅 되지만 초기화는 안 됨 (TDZ 상태)
console.log(b); // TDZ에 있어서 에러
let b = 2; // 여기서 비로소 초기화
함수 호이스팅
함수는 변수보다 호이스팅에서 더 흥미로운 동작을 보인다.
함수 선언문 (Function Declaration)
sayHello(); // "안녕하세요!" (에러 없이 동작!)
function sayHello() {
console.log("안녕하세요!");
}
함수 선언문은 전체가 호이스팅되므로 선언 전에 호출할 수 있다.
// 호이스팅 후
function sayHello() { // 전체가 맨 위로
console.log("안녕하세요!");
}
sayHello(); // "안녕하세요!"
함수 표현식 (Function Expression)
sayHi(); // TypeError: sayHi is not a function
var sayHi = function() {
console.log("Hi!");
};
함수 표현식은 변수 호이스팅 규칙을 따른다.
// 호이스팅 후
var sayHi; // 변수 선언만 호이스팅
sayHi(); // undefined는 함수가 아니므로 에러
sayHi = function() { // 함수 할당은 원래 위치
console.log("Hi!");
};
화살표 함수
greet(); // TypeError: greet is not a function
const greet = () => {
console.log("Hello!");
};
화살표 함수도 함수 표현식이므로 같은 규칙을 따른다.
재밌는(?) 예제들
변수와 함수의 우선순위
console.log(typeof foo); // "function"
var foo = "변수";
function foo() {
return "함수";
}
console.log(typeof foo); // "string"
왜 이렇게 될까?
// 호이스팅 후
function foo() { // 함수 선언이 먼저
return "함수";
}
var foo; // 변수 선언 (이미 foo가 있으므로 무시됨)
console.log(typeof foo); // "function"
foo = "변수"; // 할당
console.log(typeof foo); // "string"
반복문에서의 var vs let
// var 사용
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 출력: 3, 3, 3
// let 사용
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100);
}
// 출력: 0, 1, 2
var는 함수 스코프이므로 호이스팅되어 하나의 i를 공유하지만, let은 블록 스코프이므로 각 반복마다 새로운 변수가 생성된다.
클래스의 호이스팅
const dog = new Dog(); // ReferenceError: Cannot access 'Dog' before initialization
class Dog {
constructor(name) {
this.name = name;
}
}
클래스도 let, const처럼 TDZ의 영향을 받는다.
호이스팅 비교표
| 선언 방식 | 호이스팅 여부 | 초기화 | TDZ | 스코프 |
| var | O | undefined | X | 함수 스코프 |
| let | O | 안 됨 | O | 블록 스코프 |
| const | O | 안 됨 | O | 블록 스코프 |
| 함수 선언문 | O | 전체 | X | 함수 스코프 |
| 함수 표현식 | 변수 규칙 따름 | 변수 규칙 따름 | 변수 규칙 따름 | 변수 규칙 따름 |
| 클래스 | O | 안 됨 | O | 블록 스코프 |
'Learning Log' 카테고리의 다른 글
| [멋사 클라우드 5기] Day 20 - JavaScript의 비동기 (0) | 2026.02.25 |
|---|---|
| [멋사 클라우드 5기] Day 19 - Object.assign과 structuredClone에 대하여 (0) | 2026.02.24 |
| [멋사 클라우드 5기] Day 17 - Flexbox (0) | 2026.02.13 |
| [멋사 클라우드 5기] Day 16 - 브라우저의 동작 원리: 주소창에서 화면 렌더링까지 (0) | 2026.02.12 |
| [멋사 클라우드 5기] Day 15 - MVC & JDBC 구조 톺아보기 (0) | 2026.02.11 |
