[JS] ES6 변수와 호이스팅(Hoisting)

2025. 5. 10. 02:43Language/JS (JavaScript)

자바스크립트에서 호이스팅

호이스팅은 자바스크립트에서 코드가 실행되기 전에 변수, 함수, 클래스, import 등의 선언문이 해당 스코프의 맨 위로 끌어올려진 것처럼 동작하는 현상을 의미함. 실제로 코드가 물리적으로 이동하는 것은 아니지만, 자바스크립트 엔진이 실행 전에 전체 코드를 한 번 스캔하면서 선언을 미리 처리하기 때문에 이런 현상이 발생하는 것.

즉, 변수나 함수의 선언만 호이스팅되고(= 끌어올려지고) 변수의 값 할당이나 함수의 내용은 원래 위치에 남아 있음.
(자바스크립트는 변수 생성(Instantiation)과 초기화(Initialization)의 작업이 분리돼서 진행됨)

→ 개발자가 어느 라인, 어디 위치에서 변수, 함수를 선언해도 실행되기 전 코드가 최상단으로 끌어올려지고 실행됨.

변수 호이스팅

선언방식 호이스팅 여부 초기화 상태 선언 전 접근 시 결과
var O undefined로 초기화 undefined 반환
let / const O 초기화되지 않음(TDZ) ReferenceError(참조 오류)
  • var로 선언한 변수 : 선언만 끌어올려지고, 값은 undefined로 초기화 (선언과 동시에 undefined로 초기화)
  • let과 const로 선언한 변수 : 호이스팅되지만 선언 전 접근 시 오류 발생(일시적 사각지대로 인한 ReferenceError)
console.log(a); // undefined
var a = 10;

console.log(b); // ReferenceError >> Uncaught ReferenceError: b is not defined
let b = 20;

✨일시적 사각지대(TDZ, Temporal Dead Zone)란?
TDZ는 초기화되지 않은 변수의 사용을 방지하여, 예기치 않은 버그를 줄이고 코드의 안전성을 높이기 위해 도입된 개념으로, 자바스크립트에서 let이나 const로 선언된 변수가 스코프의 시작점부터 변수의 초기화가 이루어지기 전까지 존재하는 구간을 의미함. 이 구간에서는 변수가 이미 스코프에 등록되어 있지만, 아직 메모리에 값이 할당되지 않은 상태이기 때문에 해당 변수에 접근하려고 하면 ReferenceError가 발생함.
* 적용 대상: let과 const로 선언된 변수에만 해당함. var로 선언된 변수는 선언과 동시에 undefined로 초기화되어 TDZ가 존재하지 않음
* 발생 위치: 변수의 스코프가 시작되는 시점(예: 블록의 시작)부터 변수 선언문이 실행되어 초기화되기 전까지의 구간
* 오류 발생: TDZ 구간에서 해당 변수에 접근하면 ReferenceError 발생

→ 변수 선언과 초기화 사이의 안 보이는 위험 구간으로, 이 구간에서는 변수에 접근할 수 없음. (let과 const에서만 발생, var는 적용 안 됨)

함수 호이스팅

  • 함수 선언문 : 함수 전체가 호이스팅되어 선언 전에 호출해도 정상 동작함
  • 함수 표현식 : 변수에 할당되는 함수는 변수 선언만 호이스팅되고, 값(함수)은 호이스팅되지 않으므로 선언 전에 호출하면 오류 발생
    → 함수 표현식(특히 const나 let으로 선언된)은 변수처럼 동작해서 TDZ 영향을 받음
    값(함수)의 호이스팅이 강제적이지 않음
//함수 선언문
foo(); // 정상 실행

function foo() {
  console.log("hello");
}

//함수 표현식
// ❌ TypeError: sayHello is not a function
// var → 함수 참조는 undefined

sayHello(); 

var sayHello = function() {
  console.log("Hello");
};

// ❌ ReferenceError: sayHello is not defined
// let, const → TDZ로 ReferenceError

sayHello(); 

let sayHello = function() {
  console.log("Hello");
};

⚠️함수 선언식 vs 함수 표현식
함수 선언식과 함수 표현식 중 어느 것이 정답이다라고 정해진 것은 아님. 함수 자체는 어디에 선언하든 함수이므로 큰 문제는 아니기 때문. 때에 따라 더 좋은 선택지를 선택하는 게 좋을 듯

스코프(함수 스코프(var) vs 블록 스코프(let, const))

선언 방식 스코프 종류 블록 내 선언 시 블록 밖 접근 함수 내 선언 시 함수 밖 접근
var 함수 레벨 가능 불가능
let / const 블록 레벨 불가능 불가능

var : 함수 레벨 스코프

  • var로 선언한 변수는 함수 레벨 스코프를 가짐
  • 함수 내부에서 선언된 var 변수는 함수 안에서만 접근할 수 있음
  • 함수 외부(전역) 또는 블록({ ... }) 내부에서 선언된 var 변수는 블록을 무시하고 전역 또는 함수 스코프에 속함
    → 블록 안에서 선언해도 바깥에서 접근 가능(= 함수 단위로만 스코프 제한, 블록은 무시)
// 블록 내부에서 선언
if (true) {
  var x = 1;
}
console.log(x); // 1 (블록 밖에서도 접근 가능)

// 함수 내부에서 선언
function test() {
  var y = 2;
  console.log(y); // 2
}
console.log(y); // ReferenceError: y is not defined

 

let/const : 블록 레벨 스코프

  • let과 const로 선언한 변수는 블록 레벨 스코프를 가짐
  • 블록({ ... }) 내부에서 선언된 변수는 해당 블록 내에서만 접근할 수 있음(블록을 벗어나면 참조 불가)
    → 블록 단위로 스코프 제한 더 안전하게 변수 관리 가능
if (true) {
  let a = 10;
  const b = 20;
  console.log(a, b); // 10 20
}
console.log(a); // ReferenceError: a is not defined
console.log(b); // ReferenceError: b is not defined

클로저(Closure)

클로저 : 함수가 자신이 선언된 렉시컬 스코프(lexical scope)를 기억하고 접근할 수 있는 현상으로, 외부 함수의 변수에 접근 가능하며 주로 상태 유지/캡슐화에 사용됨.
- 외부 함수가 반환된 후에도 내부 함수가 외부 변수를 참조
- [[Environment]] 속성으로 렉시컬 환경을 유지

주 사용 예시
- 비공개 변수 생성: 외부에서 접근 불가능한 변수 보호
- 이벤트 핸들러: 콜백 함수가 외부 상태를 기억
- React 상태 관리: useState 훅에서 클로저 활용

클로저와 호이스팅의 상호작용

  1. 함수 선언문 호이스팅 : 클로저를 생성하는 외부 함수가 호이스팅되면, 내부 함수도 선언 전 사용 가능
  2. TDZ와 클로저 : let/const로 선언된 변수를 클로저가 참조할 때, TDZ 구간에서는 접근 불가
  3. var의 함수 스코프 : var 변수를 클로저가 참조하면 블록을 무시해서 예측 불가능하게 동작할 수 있음
// 함수 선언문 호이스팅
const inner = outer(); // outer는 함수 선언문
function outer() { return function() {} }

// TDZ와 클로저
{
  const closure = () => console.log(x); // ReferenceError (TDZ)
  let x = 10;
  closure(); // 10
}

// var의 함수 스코프 
// 루프에서 var를 쓰면 예상치 못한 동작
// >> var는 함수 스코프이기 때문에 setTimeout 내부 함수는 모두 같은 i를 참조함
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); 
}
// 출력: 3, 3, 3

// let을 쓰면 각 루프마다 새 스코프 생성됨
// let은 블록 스코프이므로 매 반복마다 새로운 i를 캡처함 
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 출력: 0, 1, 2

 

결론

  • 변수와 함수는 가급적 코드 상단에서 선언해 예상하지 못한 동작/오류를 방지하는 게 좋음
  • var 대신 let, const 사용
  • 함수 표현식 사용 권장