포스트

호이스팅 (Hoisting)

호이스팅(Hoisting)은 변수와 함수의 선언이 해당 스코프의 최상단으로 끌어올려지는 행동을 말합니다.

호이스팅은 코드가 실제로 실행되는 순서와 다르게 동작할 수 있기 때문에 혼란을 줄 수 있습니다.

변수 호이스팅

JavaScript에서 변수는 var, let, const로 선언할 수 있습니다.

이 중 var로 선언된 변수는 호이스팅의 영향을 받습니다.

letconst로 선언된 변수는 호이스팅의 영향을 받지만, 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 호이스팅

letconst로 선언된 변수도 호이스팅되지만, 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 선언은 호이스팅되지만, 함수 자체는 할당되지 않았기 때문에 myFunctionundefined로 평가됩니다.

조건문과 반복문에서의 호이스팅

조건문과 반복문에서도 호이스팅이 발생할 수 있습니다.

조건문 내에서 변수 선언이 호이스팅될 때, 선언된 변수는 블록 스코프가 아닌 함수 또는 전역 스코프에서 호이스팅됩니다.

1
2
3
4
if (true) {
  var x = 5;
}
console.log(x); // 5

위 코드에서 var x 선언은 조건문 내에서 이루어졌지만, 호이스팅에 의해 함수 또는 전역 스코프의 최상단으로 끌어올려집니다. 따라서 조건문 밖에서도 x에 접근할 수 있습니다.

반면에 letconst로 선언된 변수는 블록 스코프를 가지므로 조건문 밖에서는 접근할 수 없습니다.

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

반복문 내에서 함수 생성 시의 호이스팅

반복문 내에서 함수를 생성할 때, varlet의 차이는 더욱 두드러집니다.

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가 됩니다.

문제점

  1. 초기화되지 않은 변수 접근: 변수가 호이스팅되어 undefined로 초기화되기 때문에, 변수에 값을 할당하기 전에 접근하게 되면 의도하지 않은 결과를 초래할 수 있습니다.
  2. 함수 표현식의 예기치 않은 동작: 함수 표현식은 변수의 호이스팅 규칙을 따르기 때문에, 함수가 정의되기 전에 호출하면 TypeError가 발생합니다.
  3. 변수의 재선언재할당 문제: var는 변수의 재선언이 가능하기 때문에, 의도하지 않은 재할당이 발생할 수 있습니다.
  4. 함수와 변수 간의 이름 충돌: 함수와 변수의 이름이 동일할 때, 호이스팅으로 인해 예기치 않은 동작이 발생할 수 있습니다.

요약

  • var로 선언된 변수는 호이스팅되며, 선언 전에 접근하면 undefined가 반환됩니다.
  • letconst로 선언된 변수도 호이스팅되지만, TDZ(Temporal Dead Zone) 때문에 선언 전에 접근하면 ReferenceError가 발생합니다.
  • 함수 선언은 호이스팅되어 선언 전에 호출이 가능합니다.
  • 함수 표현식은 변수 호이스팅과 동일하게 동작하여, 선언 전에 호출하면 TypeError가 발생합니다.
  • 반복문 내에서도 변수 호이스팅이 발생하며, varlet의 차이에 따라 동작이 달라질 수 있습니다.
이 기사는 저작권자의 CC BY-NC-ND 4.0 라이센스를 따릅니다.