Programming Language/JavaScript

호이스팅? 생각보다 쉽습니다

dev-power 2025. 12. 31. 20:29

호이스팅의 개념


호이스팅(Hoisting)은 영어로 "끌어올리다"라는 뜻입니다.
 
쉽게 비유하자면, 선생님이 출석을 부를 때를 생각해보면 됩니다. 선생님은 수업 시작하기 전에 이미 출석부에 학생 이름이 다 적혀있어야 합니다. 수업 중간에 "어? 이 학생 이름이 없네?"라고 하면 안됩니다.
 
JavaScript도 마찬가지입니다. 코드를 실행하기 전에 JavaScript 엔진이 코드를 쭉 훑어보면서 "어떤 변수들과 함수들이 있는지"를 미리 파악합니다. 이때 변수와 함수 선언을 코드의 최상단으로 끌어올리는 것처럼 동작하는 게 호이스팅입니다.
 

정확한 정의: 변수 및 함수 선언이 스코프(유효범위)의 최상단으로 끌어올려지는 JavaScript 동작 방식

 
 
 

호이스팅의 의의


왜 이런게 있을까요? 호이스팅은 JavaScript의 실행 컨텍스트 생성 과정에서 자연스럽게 발생하는 현상입니다.
 

장점:

  • 함수를 선언하기 전에 호출할 수 있어서 코드 구조를 유연하게 작성할 수 있다.
  • 코드 가독성을 높일 수 있다 (메인 로직을 위에, 세부 함수들을 아래에 배치)

단점:

  • 예상치 못한 버그를 만들 수 있다.
  • 코드의 실행 순서가 직관적이지 않을 수 있다.

 
무슨 말일까요? 코드로 이해해봅시다.
 

// 예제 1: var 호이스팅 기본
console.log(name)  // undefined (에러가 아닙니다!)
var name = '철수'
console.log(name)  // '철수'

/*
위 코드는 JavaScript 엔진이 이렇게 해석합니다:
var name; 	// 선언부만 끌어올림
console.log(name) 	// undefined (선언은 됐지만 값이 아직 안 들어감)
name = '철수' 	// 할당은 원래 위치에서
console.log(name) 	// '철수'
*/

 
가장 쉬운 예시인데 이해가 되실까요? 다음은 함수에서의 호이스팅입니다. 함수 호이스팅은 함수 선언식과 함수 표현식에서 각각 동작하는 방식이 다릅니다.

function sum(x, y) { return x + y } -> 함수 선언식

var sum = function (x, y) { return x + y } -> 함수 표현식
// 예제 2: 함수 선언식 - 전체가 호이스팅
console.log(add(2, 3)) 	// 5 (작동합니다)

function add(x, y) {
  return x + y
}

/*
JavaScript 엔진이 이렇게 해석합니다:
function add(x, y) {
  return x + y
}
console.log(add(2, 3))
*/
// 예제 3: 함수 표현식 - 변수 선언만 호이스팅
console.log(substract) 	// undefined
console.log(subtract(5, 2) 	// TypeError: subtract is not defined

var subtract = function(x, y) {
  return x - y
}

/*
JavaScript 엔진이 이렇게 해석합니다:
var subtract
console.log(subtract)
console.log(subract(5, 2))
subract = function(x, y) {
  return x - y
}
*/

 
함수 선언식에서 에러가 발생하지 않으니 더 좋아보이나요? 천만에요! 함수 표현식이 더 예측 가능하고 안전합니다. 함수는 선언 후에만 호출할 수 있다는 것이 더 직관적입니다.
 
다소 극단적인 예시이긴 하지만, 여러 개발자의 협업에서 왜 함수 선언식이 문제를 야기할 수 있는지 예시 코드를 살펴보겠습니다. 코드를 직접 작성하면서 로그를 살펴보면 재미있을 것입니다. 혹시 이해가 가지 않는다면 댓글 남겨주세요 :)
 

// 예제 4: 함수 선언식이 야기하는 호이스팅 문제
console.log(sum(3, 4));

function sum (x, y) {  // 개발자 A가 선언한 sum 함수
	return x + y;
}

var a = sum(1, 2);

function sum (x, y) {  // 개발자 B가 선언한 sum 함수
	return x + ' + ' + y + ' = ' + (x + y);
}

var c = sum(1, 2);
console.log(c)

 
 
 

var/let/const 에서의 호이스팅


실제 개발에서는 var을 쓰지 않죠. 자, 이제 let과 const를 알아보겠습니다. let과 const도 호이스팅이 됩니다! 하지만 var와는 다르게 동작합니다.
 

// 예제 5: var의 호이스팅
console.log(a) 	// undefined
var a = 10
console.log(a) 	// 10
// 예제 6: let의 호이스팅
console.log(b) 	// ReferenceError: Cannot access 'b' before initialization
let b = 20
console.log(b) 	// 20
// 예제 7: const의 호이스팅
console.log(c) 	// ReferenceError: Cannot access 'c' before initialization
const c = 30
console.log(c) 	// 30

 
"어? let과 const는 호이스팅이 안 되는 거 아니에요?"
 
아니에요! 호이스팅은 되는데, TDZ(Temporal Dead Zone) 때문에 접근할 수 없는 것입니다.
 
 
 

TDZ(Temporal Dead Zone) 이해


TDZ는 "일시적 사각지대"라는 뜻입니다.
 
비유하자면, 택배가 집 앞에 도착했지만(호이스팅됨) 아직 봉인 테이프가 붙어있어서 열 수 없는 상태입니다. 정해진 시간(선언문)이 되어야 테이프를 뜯고 사용할 수 있습니다.
 

// 예제 8: TDZ 이해하기
// TDZ 시작 (스코프 시작)
console.log('1. 스코프 시작')

// 여기는 TDZ 구간 - myName에 접근하면 에러!
// console.log(myName) 	// ReferenceError

let myName = '지수' 	// TDZ 끝! 이제 사용 가능
console.log('2. myName:', myName) 	// '지수'
// 예제 9: TDZ를 더 명확하게 보기
function example() {
  // TDZ 시작
  console.log('함수 시작')
  
  // 이 구간은 TDZ - value에 접근 불가
  // console.log(value) 	// ReferenceError
  
  let value = 100 	// TDZ 끝
  console.log(value) 	// 100
}

example()
// 예제 10: 종합 비교
console.log('== var ==')
console.log(varVariable)	// undefined (호이스팅 O, 초기화 O)
var varVariable = 'var'
console.log(varVariable) 	// 'var'

console.log('== let ==')
// console.log(letVariable) 	// ReferenceError (호이스팅 O, 초기화 X - TDZ)
let letVariable = 'let'
console.log(letVariable) 	// 'let'

console.log('== const ==')
// console.log(constVariable) 	// ReferenceError (호이스팅 O, 초기화 X - TDZ)
const constVariable = 'const'
console.log(constVariable) 	// 'const'

 
 
 

실전 문제


// 문제 1: 출력 결과는?
function test1() {
  console.log(a)
  console.log(b)
  
  var a = 10
  let b = 20
}

test1()
// 문제 2: 출력 결과는?
var name = '전역'

function test() {
  console.log(name)
  var name = '지역'
  console.log(name)
}

test2()