TDZ(Temporal Dead Zone)가 필요한 이유는?

let과 const의 안전장치인 TDZ가 도입된 배경과 TDZ 구간에서 발생하는 오류를 통해 안전한 코드 작성법을 학습합니다

중급 15분 TDZ Temporal Dead Zone let const 안전장치

TDZ(Temporal Dead Zone)는 ES6에서 let과 const가 도입되면서 함께 등장한 개념으로, 변수가 선언되기 전까지 접근할 수 없는 시간적 구간을 의미합니다. var와 달리 let과 const는 선언 이전에 접근하면 ReferenceError를 발생시키는데, 이는 버그가 아니라 의도된 안전장치입니다. 호이스팅은 여전히 발생하지만 TDZ라는 보호 구간을 두어 초기화되지 않은 변수 접근을 차단함으로써 예측 가능한 코드 작성을 강제합니다. 이 메커니즘을 이해하지 못하면 “변수가 호이스팅되는데 왜 에러가 발생하는가?”라는 혼란에 빠지게 되며, TDZ 관련 오류를 마주했을 때 근본 원인을 파악하지 못하고 임시방편으로 코드를 수정하게 됩니다.

핵심 특징

  • 시간적 사각지대: 스코프 시작부터 변수 선언문까지의 구간에서 변수에 접근할 수 없음
  • ReferenceError 발생: TDZ 구간에서 변수에 접근하면 런타임 오류가 발생하여 즉시 문제를 감지 가능
  • 호이스팅과의 관계: let/const도 호이스팅되지만 초기화는 선언문 실행 시점에 이루어짐
  • 블록 스코프와 결합: 블록 단위로 TDZ가 형성되어 함수 스코프보다 세밀한 변수 생명주기 관리
  • const의 엄격함: const는 TDZ 외에도 선언과 동시에 초기화를 강제하여 더 안전한 상수 관리

실무에서의 영향

TDZ를 이해하면 let과 const를 사용할 때 발생하는 ReferenceError의 원인을 정확히 파악할 수 있습니다. 특히 조건문이나 반복문 내부에서 변수 선언 순서를 잘못 배치했을 때, 또는 함수 매개변수 기본값에서 다른 매개변수를 참조할 때 TDZ 오류가 자주 발생합니다. 이러한 오류는 개발 단계에서 즉시 발견되기 때문에 var 시절처럼 undefined 값이 전파되어 나중에 원인을 추적하기 어려운 버그로 발전하는 것을 방지합니다. 코드 리뷰 시 변수 선언 위치가 적절한지 판단하는 기준이 되며, 클래스 필드나 모듈 스코프에서도 TDZ 개념을 적용하여 안전한 초기화 패턴을 설계할 수 있습니다. TDZ는 단순한 오류 메커니즘이 아니라 JavaScript가 더 예측 가능하고 안전한 언어로 진화하기 위한 핵심 설계 결정이며, 이를 이해하면 ES6 이후 변수 선언의 철학을 깊이 있게 파악할 수 있습니다.


핵심 개념

TDZ(Temporal Dead Zone)란 무엇인가

입문

TDZ는 ‘시간적 사각지대’라는 뜻으로, 변수가 선언되기 전까지는 그 변수를 절대 볼 수 없는 특별한 구간이에요.

📦 금고에 넣어둔 선물 생일 선물을 미리 받았지만 생일날까지 금고에 잠겨있다고 상상해보세요. 선물은 이미 존재하지만, 생일이 되기 전까지는 절대 열어볼 수 없어요. TDZ도 마찬가지로 변수는 이미 존재하지만 선언문을 만나기 전까지는 접근할 수 없는 ‘잠긴 상태’에요.

⏰ 시간의 문제 TDZ의 특별한 점은 ‘공간’이 아니라 ‘시간’으로 구분된다는 거예요. 마치 영화 시작 전에는 극장에 들어갈 수 없는 것처럼, 코드가 실행되는 시간 순서상 선언문이 실행되기 전까지는 변수를 사용할 수 없어요.

🚨 왜 이런 규칙이 생겼을까? 옛날 방식(var)에서는 변수를 선언 전에 사용해도 에러가 안 나고 undefined라는 애매한 값이 나왔어요. 이게 나중에 큰 문제를 일으켰죠. 그래서 새로운 방식(let, const)에서는 “선언 전에 쓰면 바로 에러!”라고 명확하게 알려주는 TDZ를 만들었어요.

💡 TDZ가 끝나는 순간 TDZ는 변수 선언문이 실행되는 바로 그 순간 끝나요. 마치 금고 비밀번호를 입력하는 순간 문이 열리는 것처럼, let x = 10; 이 실행되면 TDZ가 끝나고 그때부터 x를 자유롭게 쓸 수 있어요.

중급

TDZ(Temporal Dead Zone)는 변수가 스코프의 시작 지점부터 실제 선언문이 실행되는 시점까지의 시간적 구간을 의미합니다. 이 구간에서 변수에 접근하면 ReferenceError가 발생합니다.

TDZ의 시간적 범위

  • 시작: 블록 스코프 진입 시점 (중괄호 { 를 만나는 순간)
  • 종료: let 또는 const 선언문 실행 시점
  • 특징: 코드의 물리적 위치가 아닌 실행 순서에 따라 결정됨
{
  // TDZ 시작 - x는 존재하지만 접근 불가
  console.log(x); // ReferenceError: Cannot access 'x' before initialization

  let x = 10; // TDZ 종료 - 이 순간부터 x 사용 가능
  console.log(x); // 10
}

var와의 비교 var는 TDZ가 없어서 선언 전에 접근하면 undefined를 반환합니다. 이는 오류를 숨기고 나중에 디버깅을 어렵게 만드는 원인이 됩니다.

{
  console.log(a); // undefined - 오류 없음 (문제!)
  var a = 1;

  console.log(b); // ReferenceError - 즉시 오류 감지 (안전!)
  let b = 2;
}

심화

TDZ는 ECMAScript 2015 명세에서 let과 const의 어휘적 바인딩(Lexical Binding)을 정의하면서 도입된 메커니즘으로, 변수의 생명주기를 선언(Declaration), 초기화(Initialization), 할당(Assignment) 세 단계로 분리하여 관리합니다.

ECMAScript 명세 기반 TDZ 메커니즘 ECMAScript 2023, Section 13.3.1 (Let and Const Declarations)에 따르면, let과 const 선언은 다음과 같은 단계를 거칩니다:

  1. 선언 단계 (Declaration Phase): 스코프 진입 시 변수가 환경 레코드(Environment Record)에 등록됩니다. 이때 바인딩은 생성되지만 초기화되지 않은 상태(uninitialized)로 표시됩니다.

  2. TDZ 구간: 바인딩이 uninitialized 상태일 때, GetBindingValue 추상 연산은 ReferenceError를 throw합니다. 이는 Section 9.1.1.1.6의 명세에 정의되어 있습니다.

  3. 초기화 단계 (Initialization Phase): 선언문 실행 시점에 바인딩이 undefined로 초기화됩니다. const의 경우 선언과 동시에 값이 할당되므로 이 단계에서 바로 값이 설정됩니다.

var와 let/const의 생명주기 차이

  • var: 선언과 초기화가 동시에 발생 (호이스팅 시 undefined로 초기화)
  • let: 선언은 호이스팅되지만 초기화는 선언문 실행 시점
  • const: let과 동일하나 초기화와 동시에 불변 바인딩 생성

V8 엔진의 TDZ 구현 V8 엔진에서 TDZ는 변수 상태를 추적하는 플래그를 통해 구현됩니다:

Context Slot Allocation: let/const 변수는 컨텍스트 슬롯에 할당되며, 초기화 전에는 특수한 the_hole 값으로 표시됩니다. 이 값에 접근하려는 시도는 즉시 ReferenceError를 발생시킵니다.

Static Analysis Optimization: TurboFan 최적화 컴파일러는 정적 분석을 통해 TDZ 위반을 컴파일 시점에 감지할 수 있습니다. 예를 들어, 선언문보다 앞에서 변수를 사용하는 코드는 데드 코드로 표시되어 최적화 과정에서 제거될 수 있습니다.

TDZ의 설계 철학 TDZ는 단순한 오류 메커니즘이 아니라 JavaScript를 더 안전한 언어로 만들기 위한 의도적 설계입니다. 초기화되지 않은 변수 접근을 런타임에 즉시 감지함으로써 “Fail Fast” 원칙을 구현하며, 이는 디버깅 시간을 단축하고 코드 품질을 향상시킵니다. TypeScript와 같은 정적 타입 시스템과 결합하면 TDZ 오류를 컴파일 시점에 미리 발견할 수 있어 더욱 강력한 안전성을 제공합니다.

호이스팅과 TDZ의 관계

입문

많은 사람들이 “let과 const는 호이스팅이 안 된다”고 생각하지만, 사실은 호이스팅이 되고 있어요! 다만 안전장치가 추가된 거죠.

🎈 풍선의 비유 호이스팅은 풍선이 위로 올라가는 것과 같아요. var로 만든 풍선은 올라가면서 바로 “undefined”라는 메시지를 붙이고, let으로 만든 풍선도 올라가지만 투명한 상자에 갇혀서 볼 수만 있고 만질 수는 없어요. 이 투명한 상자가 바로 TDZ예요!

👻 보이지만 만질 수 없는 유령 let이나 const로 선언한 변수는 호이스팅되어서 JavaScript는 그 변수가 있다는 걸 알고 있어요. 하지만 TDZ라는 보호막이 쳐져있어서 우리가 접근하려고 하면 “아직 안 돼!”라고 에러를 내보내는 거예요.

🔒 이중 잠금 장치 옛날 방식(var)은 자물쇠 하나만 있어서 호이스팅되면 바로 열렸어요(undefined). 새로운 방식(let, const)은 자물쇠가 두 개예요. 첫 번째는 호이스팅으로 풀리지만, 두 번째 자물쇠(TDZ)는 선언문을 만나야만 풀려요!

💡 왜 이렇게 복잡하게 했을까? 호이스팅 자체는 JavaScript 엔진이 효율적으로 일하기 위해 필요한 거예요. 하지만 TDZ를 추가해서 “호이스팅은 되지만 사용은 못 하게” 만든 건, 우리가 실수로 초기화 안 된 변수를 쓰는 걸 막아주려는 거예요. 더 안전하게!

중급

호이스팅(Hoisting)은 변수 선언이 스코프의 최상단으로 끌어올려지는 JavaScript의 내부 동작입니다. let과 const도 호이스팅되지만, var와 달리 TDZ로 인해 선언문 이전에는 접근할 수 없습니다.

호이스팅의 공통점

  • var, let, const 모두 호이스팅됨
  • 스코프 진입 시 변수가 환경 레코드에 등록됨
  • 코드 실행 전 선언 단계가 먼저 처리됨

초기화의 차이점

  • var: 호이스팅 시 undefined로 자동 초기화
  • let/const: 호이스팅되지만 초기화되지 않음 (TDZ 상태)
{
  // 두 변수 모두 호이스팅되어 스코프에 등록됨
  console.log(typeof a); // "undefined" - 초기화됨
  console.log(typeof b); // ReferenceError - 초기화 안 됨 (TDZ)

  var a = 1;
  let b = 2;
}

typeof 연산자의 특이 케이스 일반적으로 typeof는 선언되지 않은 변수에 대해 “undefined”를 반환하지만, TDZ 구간의 let/const 변수에 대해서는 ReferenceError를 발생시킵니다. 이는 변수가 이미 스코프에 등록되었음을 증명합니다.

let x = 'outer';

{
  // 만약 호이스팅이 안 된다면 outer를 출력해야 함
  // 하지만 ReferenceError 발생 = 내부 x가 이미 호이스팅됨
  console.log(x); // ReferenceError
  let x = 'inner';
}

심화

호이스팅과 TDZ의 관계는 ECMAScript 명세의 실행 컨텍스트(Execution Context) 생성 과정에서 명확히 드러납니다. 모든 선언은 호이스팅되지만, 초기화(Initialization) 시점이 다르기 때문에 TDZ 발생 여부가 결정됩니다.

ECMAScript 명세 기반 호이스팅 메커니즘 Section 9.2.12 (FunctionDeclarationInstantiation)에 따르면, 함수 또는 블록 진입 시 다음 순서로 환경이 구성됩니다:

  1. Environment Record 생성: 새로운 선언적 환경 레코드 생성
  2. Binding 생성: 모든 var, let, const 선언에 대해 바인딩 생성
  3. 초기화 차별화:
    • var: CreateMutableBinding 호출 후 즉시 undefined로 초기화
    • let/const: CreateMutableBinding 호출하지만 초기화 미수행

TDZ를 통한 호이스팅 증명 사례 다음 코드는 let이 호이스팅되지 않는다면 외부 스코프의 x를 참조해야 하지만, 실제로는 ReferenceError가 발생합니다:

const x = 1;
{
  console.log(x); // 호이스팅 안 되면 1 출력, 실제로는 ReferenceError
  const x = 2;
}

이는 내부 스코프의 x가 이미 호이스팅되어 바인딩이 생성되었지만, TDZ 상태이기 때문에 접근이 차단됨을 의미합니다.

V8 엔진의 구현 상세 V8의 Ignition 인터프리터는 다음과 같이 TDZ를 구현합니다:

CreateBlockContext 바이트코드: 블록 스코프 진입 시 모든 let/const 선언을 스캔하여 컨텍스트 슬롯을 예약합니다. 이 시점에 슬롯은 the_hole 값으로 초기화됩니다.

LdaLookupSlot 바이트코드: 변수 접근 시 슬롯 값을 확인하여 the_hole이면 ThrowReferenceError 헬퍼를 호출합니다.

StaContextSlot 바이트코드: 선언문 실행 시 실제 값으로 슬롯을 업데이트합니다.

성능 특성 TDZ 검사는 추가 비용이 발생하지만, 최적화 컴파일러는 정적 분석을 통해 대부분의 검사를 제거할 수 있습니다. TurboFan은 선언문 이후의 모든 접근에 대해 TDZ 검사를 생략하는 최적화를 수행하며, 벤치마크 결과 최적화된 코드에서는 var와 let의 성능 차이가 1% 미만입니다.

const의 TDZ와 불변성

입문

const는 let보다 더 엄격한 규칙을 가지고 있어요. TDZ뿐만 아니라 “한 번 정하면 절대 못 바꿔!”라는 규칙도 있죠.

📌 영구 스티커 let은 메모지에 연필로 쓴 것처럼 나중에 지우고 다시 쓸 수 있어요. 하지만 const는 영구 스티커처럼 한 번 붙이면 절대 뗄 수 없어요. 그래서 붙일 때(선언할 때) 반드시 무엇을 쓸지(초기값) 정해야 해요!

🎯 선언과 동시에 값 지정 let은 “일단 선언하고 나중에 값 줄게”가 가능하지만, const는 안 돼요. 반드시 선언하는 순간 값을 정해야 해요. 마치 출생신고 할 때 이름을 비워둘 수 없는 것처럼요!

🔐 이중 보호장치 const는 두 가지 방법으로 우리를 보호해요. 첫 번째는 TDZ로 “선언 전에 쓰지 마!”라고 하고, 두 번째는 “한 번 정하면 바꾸지 마!”라고 해요. 덕분에 실수로 중요한 값을 바꾸는 걸 막아줘요.

💡 객체는 예외? const로 선언한 객체나 배열은 내용물은 바꿀 수 있어요! 마치 잠긴 보물상자는 움직일 수 없지만, 상자 안 물건은 꺼내고 넣을 수 있는 것과 같아요. const는 ‘변수 자체’를 못 바꾸게 하는 거지, ‘내용물’까지 막는 건 아니에요.

중급

const는 let과 동일한 TDZ 규칙을 따르지만, 추가로 선언과 동시에 초기화를 강제하며 재할당을 금지합니다.

const의 두 가지 제약

  1. TDZ: 선언 전 접근 시 ReferenceError
  2. 불변 바인딩(Immutable Binding): 재할당 시 TypeError

선언 시 초기화 강제 const는 선언과 동시에 값을 할당해야 합니다. 이는 TDZ와는 별개의 문법 규칙입니다.

// SyntaxError: Missing initializer in const declaration
const x; // 선언만 하면 문법 오류

// 올바른 사용
const y = 10;
console.log(y); // 10

// TypeError: Assignment to constant variable
y = 20; // 재할당 불가

참조 타입의 불변성 const는 변수 바인딩의 불변성을 보장하지, 값 자체의 불변성을 보장하지 않습니다. 객체나 배열의 경우 내부 프로퍼티는 변경 가능합니다.

const obj = { value: 1 };
obj.value = 2; // OK - 프로퍼티 변경 가능
console.log(obj.value); // 2

obj = {}; // TypeError - 재할당 불가

const arr = [1, 2, 3];
arr.push(4); // OK - 배열 메서드 사용 가능
arr = []; // TypeError - 재할당 불가

깊은 불변성(Deep Immutability) 완전한 불변성이 필요하다면 Object.freeze() 또는 Immutable.js 같은 라이브러리를 사용해야 합니다.

const obj = Object.freeze({ value: 1 });
obj.value = 2; // 무시됨 (strict mode에서는 TypeError)
console.log(obj.value); // 1

심화

const의 불변 바인딩(Immutable Binding)은 ECMAScript 명세에서 let과는 다른 바인딩 생성 메커니즘을 통해 구현되며, TDZ와는 독립적으로 작동하는 별도의 제약 사항입니다.

ECMAScript 명세 기반 const 메커니즘 Section 13.3.1.4 (Runtime Semantics: Evaluation for LexicalBinding)에 따르면:

  1. 바인딩 생성: CreateImmutableBinding 추상 연산 호출 (let은 CreateMutableBinding)
  2. 초기화: InitializeBinding으로 값 설정 (이 단계에서 TDZ 종료)
  3. 재할당 방지: SetMutableBinding이 아닌 InitializeBinding만 허용

TDZ와 초기화 강제의 관계 const가 선언 시 초기화를 강제하는 것은 문법적 제약(Syntax Rule)이지 TDZ와 직접적인 관련은 없습니다. Early Error 규칙(Section 13.3.1.1)에서 초기화 표현식이 없는 const 선언을 문법 오류로 정의합니다.

// 파싱 단계에서 SyntaxError 발생 (런타임 도달 전)
const x; // Early Error

이는 런타임 ReferenceError를 발생시키는 TDZ와는 다른 레벨의 검증입니다.

V8 엔진의 const 구현 V8은 const를 다음과 같이 구현합니다:

바인딩 플래그: 컨텍스트 슬롯에 “immutable” 플래그 설정. StaContextSlot 바이트코드 실행 시 이 플래그를 확인하여 재할당 시도를 거부합니다.

최적화: TurboFan 컴파일러는 const 변수가 절대 변하지 않음을 알고 있으므로 공격적인 인라인화(Inlining)와 상수 폴딩(Constant Folding)을 수행합니다. 이는 성능 향상으로 이어집니다.

const PI = 3.14159;
function area(r) {
  return PI * r * r; // PI가 인라인되어 3.14159 * r * r로 컴파일됨
}

참조 타입의 얕은 불변성(Shallow Immutability) const는 바인딩만 불변이므로, 객체 참조는 변경할 수 없지만 객체 내부는 변경 가능합니다. 이는 ECMAScript의 의도적 설계로, 다음 이유가 있습니다:

  1. 성능: 깊은 불변성 검사는 런타임 오버헤드가 큼
  2. 유연성: 불변 객체가 필요하면 Object.freeze() 같은 명시적 API 사용
  3. 일관성: 다른 언어(Java의 final, C++의 const)와 유사한 동작

TypeScript의 readonly와의 차이 TypeScript의 readonly는 컴파일 타임 타입 검사이고, const는 런타임 바인딩 제약입니다:

const obj: { readonly value: number } = { value: 1 };
obj.value = 2; // 컴파일 에러 (TypeScript)
// 하지만 JavaScript로 트랜스파일되면 런타임에는 변경됨

완전한 불변성을 위해서는 두 메커니즘을 모두 활용해야 합니다.

TDZ 실전 사례와 디버깅

입문

TDZ 오류는 실제 코드에서 생각보다 자주 만날 수 있어요. 어떤 상황에서 발생하는지 알아두면 당황하지 않아요!

🔍 숨은 TDZ 찾기 TDZ는 때때로 예상 못한 곳에 숨어있어요. 특히 함수 안에서 다른 함수를 부르거나, 조건문 안에서 변수를 쓸 때 조심해야 해요.

📝 매개변수의 함정 함수의 매개변수에서 다른 매개변수를 참조할 때도 TDZ가 발생할 수 있어요! 왼쪽에서 오른쪽으로 하나씩 정해지는데, 아직 정해지지 않은 매개변수를 먼저 쓰려고 하면 에러가 나요.

🎯 클래스 필드의 순서 클래스 안에서 필드를 정의할 때도 순서가 중요해요. 나중에 선언된 필드를 먼저 사용하려고 하면 TDZ 오류가 발생해요.

💡 오류 메시지 읽는 법 “Cannot access ‘x’ before initialization”이라는 메시지가 보이면 TDZ 때문이에요. 이 메시지를 보면 “아, 내가 변수를 선언하기 전에 썼구나!”라고 바로 알 수 있어요. 그럼 변수 선언을 위로 올리거나, 사용하는 코드를 아래로 내리면 해결!

🛠️ 해결 방법 TDZ 오류를 고치는 방법은 간단해요. 변수를 사용하기 전에 반드시 선언하면 돼요. 코드를 위에서 아래로 읽으면서 “이 변수가 벌써 선언되었나?”를 확인하는 습관을 들이면 좋아요!

중급

TDZ 오류는 다양한 실전 상황에서 발생할 수 있으며, 특히 함수 매개변수, 클래스 필드, 순환 참조 등에서 자주 나타납니다.

TDZ가 발생하는 주요 상황

  1. 함수 매개변수 기본값에서 다른 매개변수 참조
  2. 클래스 필드 초기화 시 다른 필드 참조
  3. 블록 스코프 내 조건부 선언
  4. typeof 연산자 사용 시
// 오류: y는 아직 초기화되지 않음
function example(x = y, y = 1) {
  return x + y;
}
example(); // ReferenceError

// 올바른 순서
function correct(y = 1, x = y) {
  return x + y;
}
correct(); // 2
class Example {
  // 오류: b는 아직 초기화되지 않음
  a = this.b;
  b = 10;
}
new Example(); // ReferenceError

// 올바른 순서
class Correct {
  b = 10;
  a = this.b;
}
new Correct(); // OK

디버깅 전략

  1. 오류 메시지 확인: “Cannot access ‘X’ before initialization”
  2. 선언 위치 확인: 변수가 사용보다 위에 선언되었는지 체크
  3. 스코프 범위 확인: 올바른 스코프에서 선언되었는지 확인
  4. 실행 순서 추적: 코드 실행 흐름상 선언이 먼저 실행되는지 검증
// 선언되지 않은 변수
console.log(typeof notDeclared); // "undefined" - 오류 없음

// TDZ 구간의 변수
console.log(typeof x); // ReferenceError - 오류 발생!
let x = 1;

심화

TDZ 관련 오류는 복잡한 코드 패턴에서 미묘하게 발생할 수 있으며, 특히 클로저(Closure), 비동기 코드, 동적 스코프 생성 등에서 예상치 못한 동작을 보일 수 있습니다.

복잡한 TDZ 시나리오

1. 클로저와 TDZ 클로저가 생성되는 시점과 실행되는 시점의 차이로 인해 TDZ 오류가 발생할 수 있습니다:

function createFunctions() {
  const functions = [];

  // 클로저 생성 시점에는 문제없음
  for (let i = 0; i < 3; i++) {
    functions.push(() => console.log(x)); // x는 아직 선언 안 됨
  }

  let x = 10;
  return functions;
}

const fns = createFunctions();
fns[0](); // 10 - 실행 시점에는 x가 초기화됨

2. 동적 임포트와 TDZ ES Module의 동적 임포트는 비동기적으로 실행되므로 TDZ와 복잡하게 상호작용합니다:

{
  const promise = import('./module.js');
  // 이 시점에서는 모듈이 로드되지 않음

  let config = await promise;
  // config는 여기서 초기화되지만, await 이전에 접근하면 TDZ
}

ECMAScript 명세의 복잡한 케이스

Parameter Default Value Evaluation Section 9.2.12의 FunctionDeclarationInstantiation에 따르면, 매개변수 기본값은 왼쪽에서 오른쪽으로 평가되며, 각 매개변수는 평가 직후 바인딩됩니다:

function f(a, b = a, c = b) {
  // a 초기화 → b = a 평가 → b 초기화 → c = b 평가 → c 초기화
}
f(1); // OK

function g(a = b, b) {
  // a = b 평가 시 b는 아직 초기화 안 됨
}
g(undefined, 1); // ReferenceError

Class Field Initialization TC39 Proposal “Class Fields”에 따르면, 클래스 필드는 constructor 실행 직전에 선언 순서대로 초기화됩니다:

class Base {
  x = this.method(); // method는 prototype에 있으므로 OK

  method() {
    return this.y; // 이 시점에 y는 TDZ
  }

  y = 10;
}

new Base(); // undefined (y가 아직 초기화 안 됨)

V8의 최적화와 TDZ TurboFan 컴파일러는 정적 분석을 통해 TDZ 검사를 최적화합니다:

  1. Static TDZ Analysis: 컴파일 타임에 TDZ 위반을 감지하면 데드 코드로 표시
  2. Dynamic TDZ Check Elimination: 제어 흐름 분석을 통해 불필요한 TDZ 검사 제거
  3. Speculation: 추측 최적화로 TDZ 검사를 지연시키고, 실제 위반 시 Deoptimization

실전 디버깅 도구

  • ESLint: no-use-before-define 규칙으로 TDZ 위반 사전 감지
  • TypeScript: 제어 흐름 분석으로 TDZ 오류를 컴파일 타임에 탐지
  • Chrome DevTools: Stack trace에서 초기화되지 않은 변수 표시

성능 고려사항 TDZ 검사는 마이크로벤치마크에서는 측정 가능하지만, 실제 애플리케이션에서는 무시할 수 있는 수준입니다. V8 팀의 벤치마크 결과, TDZ 검사 오버헤드는 전체 실행 시간의 0.1% 미만입니다.