TDZ — 등록은 됐지만 아직 쓸 수 없는 구간
바깥에 분명히 있는 변수를 읽었는데 ReferenceError 가 나는 이유. let·const 는 스코프에 들어가는 순간 등록되지만 선언문을 지나기 전까지는 손댈 수 없다.
Seobway · · 6분
바깥 변수가 있는데 왜 에러인가
바깥에 x 가 있다. 블록 안에서 x 를 읽는다. 스코프 체인을 따라 바깥 x 를
찾을 것 같은데, 에러가 난다.
let x = "바깥";
{
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = "안쪽";
}
let x = "안쪽" 줄을 지우면 "바깥" 이 잘 나온다. 아래에 있는 줄 하나가 위의 줄을
망가뜨린다. 이게 TDZ 다.
어떻게 동작하는가
호이스팅에서 본 규칙을 다시 꺼낸다. 스코프에 들어가면 코드를 실행하기
전에 그 스코프의 선언을 먼저 등록한다. 여기까지는 var 든 let 이든 같다.
다른 건 등록할 때 넣어 두는 값이다.
| 등록 시점에 들어가는 것 | 선언 전에 읽으면 | |
|---|---|---|
var |
undefined |
undefined |
let · const · class |
아무것도 없음 (초기화 안 됨) | ReferenceError |
그래서 맨 위 예제가 이렇게 풀린다.
- 블록에 들어간다. 블록 안의
let x를 먼저 등록한다(값 없음). console.log(x)— 이름을 찾는다. 블록 안에x가 이미 등록돼 있다. 바깥까지 갈 필요가 없다.- 찾은
x는 아직 값이 없다 → TDZ → 에러.
바깥 x 를 못 본 게 아니라, 안쪽 x 가 먼저 잡혀서 바깥까지 안 간 것이다.
인터랙티브 다이어그램: 블록에 들어가서 let x 줄을 지날 때까지 (단계별로 재생할 수 있다)
- 바깥에 x = "바깥" 이 있다.
- 블록에 들어가자마자 안쪽 let x 를 등록한다. 이름만 있고 값은 없다.
- console.log(x) — 가장 가까운 블록 스코프에서 x 를 찾는다. 있다. 바깥까지 가지 않는다.
- 찾은 x 가 TDZ 라서 읽을 수 없다.
- (에러가 없었다면) let x = "안쪽" 줄에 닿는 순간 값이 들어가고 TDZ 가 끝난다.
"시간" 사각지대인 이유
TDZ 는 코드 위치가 아니라 실행 순서로 정해진다. 이름에 Temporal(시간)이 붙은 이유다.
function show() {
console.log(msg); // 코드상으로는 선언보다 위에 있다
}
let msg = "안녕";
show(); // "안녕" — 에러가 아니다
show 안의 console.log(msg) 는 글자로는 let msg 보다 위에 있다. 하지만 실행되는 시점은let msg = "안녕" 을 지난 뒤다. 그때는 TDZ 가 이미 끝났으니 문제없다.show() 를 let msg 위로 올리면 그때 에러가 난다.
직접 확인
각 줄이 에러인지 아닌지 먼저 예측해 봐라. try 로 감싸 두었으니 에러가 나도 끝까지 돈다.
function check(label, fn) {
try {
console.log(label, "→", fn());
} catch (e) {
console.log(label, "→", e.name);
}
}
// 1. 선언된 적 없는 이름의 typeof 는 에러가 아니다
check("typeof 없는이름", () => typeof neverDeclared);
// 2. TDZ 안에서는 typeof 도 에러다
check("TDZ 안 typeof", () => {
const t = typeof later;
let later = 1;
return t;
});
// 3. 위치는 위지만 실행은 선언 뒤 → 괜찮다
check("나중에 호출", () => {
const read = () => value;
let value = 42;
return read();
});
// 4. 자기 자신으로 초기화 → 오른쪽을 계산할 때 아직 TDZ
check("let n = n + 1", () => {
let n = n + 1;
return n;
});
// 5. class 도 TDZ 가 있다
check("class 선언 전 new", () => {
const u = new User();
class User {}
return u;
});
2번이 특히 의외다. typeof 는 "없는 변수여도 에러 없이 "undefined"" 로 알려져 있는데,
TDZ 안의 변수는 없는 게 아니라 있는데 못 쓰는 것이라 typeof 도 막힌다.
흔한 실수
한 줄 정리
let·const·class 는 스코프에 들어가는 순간 이름만 등록되고 값은 비어 있다.
선언문이 실행될 때까지 그 이름을 건드리면 ReferenceError 다. 기준은 코드 위치가 아니라
실행 순서다.
- 바깥에 같은 이름이 있는데도 에러가 나는 이유를 설명할 수 있다
- "
let은 호이스팅되지 않는다" 가 왜 틀린 말인지 안다 - 선언보다 위에 적힌 코드가 에러 없이 도는 경우를 예로 들 수 있다
- TDZ 안에서
typeof가 에러인 이유를 안다
참고
- [1] let — Temporal dead zone (MDN Web Docs)
- [2] ReferenceError: can't access lexical declaration before initialization — MDN Web Docs
- [3] let and const Declarations — ECMAScript 사양
관련 글
- 호이스팅 — 선언은 먼저 올라간다 → — 이 글의 선행
- 스코프 — 변수가 어디까지 보이는가 → — 이름을 가장 가까운 스코프부터 찾는 규칙
- 클로저 — 함수가 붙잡고 있는 변수 →
- javascript
- TDZ
- let
- const
- 호이스팅