일시적 사각지대(Temporal Dead Zone, TDZ)

일시적 사각지대(Temporal Dead Zone, TDZ)는 JavaScript에서 변수가 선언되었지만 아직 초기화되지 않은 상태로 존재하는 코드 영역을 의미한다. 이 개념은 ES6(ECMAScript 2015)에서 letconst 키워드가 도입되면서 함께 등장했다.

TDZ는 변수가 스코프에 들어가는 시점(코드 블록의 시작)부터 해당 변수의 선언문이 실행되는 시점까지의 구간을 가리킨다. 이 구간에서는 변수가 존재하지만 접근할 수 없는 상태이다.

1
2
3
4
5
6
{
  // TDZ 시작 (x에 대한)
  console.log(x); // ReferenceError: Cannot access 'x' before initialization
  let x = 10;     // TDZ 종료 (x에 대한)
  console.log(x); // 10 (정상 작동)
}

위 예제에서 x는 블록의 시작부터 존재하지만(호이스팅됨), let x = 10이 실행되기 전까지는 접근할 수 없다. 이 기간이 바로 TDZ이다.

일시적 사각지대(TDZ)는 변수가 선언되었지만 아직 초기화되지 않은 상태로 존재하는 코드 영역이다. 이는 JavaScript에서 let, const 및 클래스 선언의 중요한 특성으로, 변수를 선언 전에 사용하는 것을 방지하여 더 안전한 코드 작성을 유도한다.
TDZ를 이해하고 이에 맞게 코드를 작성하면 예측 가능한 변수 동작을 보장하고 잠재적인 버그를 줄일 수 있다. 모던 JavaScript 개발에서는 변수를 사용하기 전에 적절히 선언하고 초기화하는 습관을 들이는 것이 좋다.

TDZ가 적용되는 대상

TDZ는 다음과 같은 선언에 적용된다:

  1. let 변수

    1
    2
    
    console.log(letVar); // ReferenceError
    let letVar = 5;
    
  2. const 변수

    1
    2
    
    console.log(constVar); // ReferenceError
    const constVar = 10;
    
  3. 클래스 선언

    1
    2
    
    console.log(new MyClass()); // ReferenceError
    class MyClass {}
    
  4. 클래스 및 함수의 기본 매개변수

    1
    2
    3
    4
    5
    
    function demo(a = b, b = 2) {
      return [a, b];
    }
    demo(); // ReferenceError: b is not defined
    // 매개변수 a의 기본값 b는 아직 TDZ에 있음
    

TDZ와 호이스팅(Hoisting)의 관계

많은 개발자들이 “letconst는 호이스팅되지 않는다"라고 오해하지만, 실제로는 이들도 호이스팅된다.
다만 var와 달리 초기화 없이 선언만 호이스팅되어 TDZ가 생성된다.

변수 생명주기의 세 단계를 살펴보면:

  1. 선언(Declaration): 변수가 스코프에 등록됨
  2. 초기화(Initialization): 변수에 메모리가 할당되고 기본값(undefined)으로 초기화됨
  3. 할당(Assignment): 실제 값이 변수에 할당됨

var는 선언과 초기화가 함께 호이스팅되어 undefined로 초기화된다:

1
2
console.log(varVar); // undefined (에러 아님)
var varVar = 5;

반면 letconst는 선언만 호이스팅되고 초기화는 호이스팅되지 않는다:

1
2
console.log(letVar); // ReferenceError: Cannot access 'letVar' before initialization
let letVar = 5;

TDZ의 목적과 중요성

TDZ는 우연히 생긴 것이 아닌, 의도적으로 설계된 JavaScript의 특성이다.
그 주요 목적은:

  1. 더 안전한 변수 사용 보장: 변수가 선언 및 초기화되기 전에 접근하는 것을 방지한다.
  2. 상수의 불변성 보장: const 변수가 항상 선언된 값을 유지하도록 한다.
  3. 코드 품질 향상: 변수를 사용하기 전에 적절히 선언하고 초기화하도록 유도한다.
  4. 버그 예방: 변수가 예기치 않게 undefined로 초기화되는 것을 방지한다.

실제 TDZ가 영향을 미치는 시나리오

블록 스코프 내에서의 TDZ

1
2
3
4
5
6
7
8
{
  const x = 1;
  {
    // x의 TDZ 시작
    console.log(x); // ReferenceError
    const x = 2;    // x의 TDZ 종료
  }
}

이 예제에서 내부 블록의 x는 외부 블록의 x를 가리지만(섀도잉), TDZ로 인해 내부 x가 선언되기 전에는 어떤 x에도 접근할 수 없다.

함수 매개변수에서의 TDZ

1
2
3
4
function example(a = b, b = 2) {
  console.log(a, b);
}
example(); // ReferenceError: b is not defined

매개변수 a의 기본값에서 b를 참조하고 있지만, b는 아직 TDZ에 있으므로 에러가 발생한다.

정적 메서드에서의 This 참조

1
2
3
4
class Example {
  static field1 = Example.field2 * 2; // ReferenceError
  static field2 = 10;
}

field1이 초기화될 때 Example.field2는 아직 TDZ에 있다.

TDZ를 피하는 방법

TDZ로 인한 문제를 피하기 위한 몇 가지 방법:

  1. 변수를 사용하기 전에 항상 선언하고 초기화하기:

    1
    2
    3
    
    // 좋은 방법
    let x = 5;
    console.log(x);
    
  2. 매개변수의 기본값에서 아직 선언되지 않은 매개변수를 참조하지 않기:

    1
    2
    3
    4
    
    // 올바른 순서
    function example(b = 2, a = b) {
      console.log(a, b);
    }
    
  3. 복잡한 초기화 로직은 선언 후 별도로 처리하기:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    const value; // SyntaxError: Missing initializer in const declaration
    
    // 대신 이렇게 하세요:
    let value;
    // 복잡한 로직…
    value = computedValue();
    
    // 또는 즉시 초기화:
    const value = (() => {
      // 복잡한 로직…
      return computedValue();
    })();
    

다양한 자바스크립트 환경에서의 TDZ

TDZ는 모든 ECMAScript 2015(ES6) 호환 환경에서 동일하게 작동하지만, 일부 트랜스파일러나 구형 브라우저에서는 구현이 다를 수 있다. Babel과 같은 트랜스파일러는 TDZ 검사를 에뮬레이션하지만, 런타임이 아닌 빌드 타임에 수행하는 경우도 있다.


참고 및 출처