함수명과 const가 겹칠 때 ReferenceError가 나는 이유

TDZ (Temporal Dead Zone) — function hoisting과 const 블록 스코프 충돌

javascriptcs

난이도: ★★★☆☆


핵심 요약

  1. 같은 블록 스코프에 동일한 이름의 const가 있으면, const 선언이 블록 최상단으로 호이스팅되어 외부 함수 이름을 블록 전체에서 가린다.
  2. const는 초기화 라인 직전까지 TDZ(Temporal Dead Zone)에 있어서, 그 전에 접근하면 ReferenceError: Cannot access 'X' before initialization이 발생한다.

왜 헷갈렸나

“함수 선언이 있으니 (A) 시점에는 외부 스코프 함수를 쓸 것”이라고 생각했음. const가 블록 전체에서 이름을 가린다는 것, 그리고 가려진 상태(TDZ)에서 접근하면 에러가 난다는 것을 몰랐음. 단순히 “같은 이름 금지” 규칙이라고만 이해하고 있었음.


메커니즘

함수 본체도 블록이다. 화살표 함수 콜백 () => { ... }{}는 블록이자 함수 경계다. const/let은 만나는 {} 마다 벽이 생기므로, 콜백 함수 최상위 레벨 const는 그 함수 본체 블록에 갇힌다.

스코프 계층:
전역 스코프
  └─ 함수 스코프 (function body {} — 함수이자 블록)
       └─ 블록 스코프 (if {}, for {}, 일반 {} 등)

var  → 함수 경계가 유일한 벽. 중간 블록 무시.
const/let → 만나는 {} 마다 벽. 함수 본체 포함.
// 외부 스코프
function isEligible(value) { return LIST.includes(value); }

// 블록 스코프 (예: useEffect 콜백 — 함수 본체 = 블록)
useEffect(() => {
  // ← 이 시점부터 isEligible은 TDZ. 외부 함수를 가림.
  //   이름은 이미 예약됐지만, 초기화 전이라 접근 불가.

  if (isEligible(inputValue)) {   // (A) ← ReferenceError 발생
    doSomething();
  }

  const isEligible =              // (B) ← 초기화 → TDZ 종료
    sessionStorage.get("flag") === "true";

  if (isEligible) { ... }
}, []);
블록 진입 시점에서 JavaScript 엔진의 처리:

외부 스코프:  function isEligible → 호이스팅, 즉시 사용 가능
블록 스코프:  const isEligible    → 이름만 등록(호이스팅), 초기화 전 → TDZ

(A) 이름 조회 → 블록 내 const isEligible 발견 → TDZ → ReferenceError
(B) 이후부터  → 정상 초기화, 이후 접근 가능
키워드호이스팅초기화 시점TDZ
varO호이스팅 즉시 (undefined)없음
function 선언O호이스팅 즉시 (함수 본체)없음
const / letO선언 라인 도달 시있음

var로 바꿔도 깨진다 — 에러 종류만 다르다

const 대신 var를 써도 동일한 이름 충돌이 발생한다. var도 함수 스코프(= 콜백 본체 블록)로 호이스팅되어 외부 함수를 가리기 때문이다. 단, 에러 메시지가 다르다:

// var 버전: TDZ 없이 undefined로 초기화됨
var isEligible = sessionStorage.get("flag") === "true";

// (A) 시점: isEligible = undefined → undefined(inputValue) 호출
// → TypeError: isEligible is not a function
키워드(A) 시점 값에러 타입원인 파악 난이도
constTDZReferenceError: Cannot access before initialization쉬움 — 직접적
varundefinedTypeError: isEligible is not a function어려움 — 우회적

constReferenceError는 “초기화 전 접근”이라는 원인을 직접 드러낸다. varTypeError는 “함수가 아닌 걸 호출했다”로 나와서 왜 undefined가 됐는지 역추적해야 한다. const/let을 선호하는 이유 중 하나.


해결 패턴

// ✅ 이름을 다르게 — 가장 단순한 해결
function checkEligibility(value: string): boolean {
  return LIST.includes(value);
}

useEffect(() => {
  if (checkEligibility(inputValue)) { ... }  // 충돌 없음

  const isEligible = sessionStorage.get("flag") === "true";
  if (isEligible) { ... }
}, []);
// ✅ const를 블록 최상단으로 올리기 — TDZ 즉시 종료
useEffect(() => {
  const isEligible = sessionStorage.get("flag") === "true";

  if (checkEligibility(inputValue)) { ... }
  if (isEligible) { ... }
}, []);

다음에 이 상황을 만나면

ReferenceError: Cannot access 'X' before initialization을 만나면:

  • 같은 블록 안에 같은 이름의 const/let이 있는가?
  • 외부 스코프 함수 이름과 블록 내 const 이름이 겹치는가?
  • 이름을 다르게 하거나, const를 블록 최상단으로 올리면 해결

커넥팅 닷

← 선행 개념 (이걸 알아야 이해된다)

  • JavaScript Hoisting — 선언이 스코프 최상단으로 올라가는 메커니즘. var vs const/let의 차이가 TDZ의 원인
  • JavaScript Scope Chain — 블록 스코프가 외부 스코프 이름을 가리는(shadowing) 원리. 블록 내 이름이 우선 탐색된다

→ 확장 개념 (여기서 더 나아가면)

  • var / let / const 호이스팅 전체 비교 — 세 키워드의 호이스팅·초기화·TDZ 타이밍 차이 전체 그림
  • 번들러 정적 분석 — TypeScript/ESLint가 TDZ 위반을 컴파일 타임에 감지하는 방식. no-use-before-define 규칙

↔ 같은 원리가 적용되는 곳

  • 비동기 반복문에서 React 상태가 꼬이는 이유 — 클로저가 스코프 변수를 캡처하는 타이밍 문제가 공통 뿌리
  • 클래스 필드 초기화 순서 — 생성자에서 필드를 선언 전에 참조하면 undefined가 되는 같은 계열의 초기화 타이밍 문제

참고