본문 바로가기

TDZ (Temporal Dead Zone)이란?

본 내용은 Don't Use Javascript Variables Without Knowing Temporal Dead Zone을 번역한 'TDZ를 모른 채 자바스크립트 변수를 사용하지 말라' 글을 기반으로 요약한 글입니다.

 

[출처]

TDZ를 모른 채 자바스크립트 변수를 사용하지 말라

 

TDZ을 모른 채 자바스크립트 변수를 사용하지 말라

간단한 질문을 하나 하겠다. 아래 코드 스니펫에서 에러가 발생할까? 첫 번째 코드는 인스턴스를 생성한 다음 클래스를 선언한다.

ui.toast.com


1. TDZ란?

 

변수 선언 이전에 변수를 참조하는 영역. 해당 영역에서 선언 이전에 참조한 변수는 참조 에러가 발생한다.

 

즉, TDZ에 영향을 받는 변수는 선언 이전에 참조하는 것을 금지하고 있다.

 

TDZ가 자바스크립트에서 필요한 이유는 동적 언어의 런타임 타입 체크를 할 수 있기 때문이다. 

 

console.log(a); // ReferenceError: Cannot access 'a' before initialization

const a = "JUJ";

console.log(a);

 

2. TDZ에 해당하는 구문

 

2-1. let, const 변수

 

일반적으로 변수의 생성과정은 다음과 같다.

 

(1) 선언

(2) 초기화

(3) 할당

 

var 키워드의 경우, 선언과 동시에 undefined로 초기화가 이루어지는 반면, let, const는 선언과 초기화가 따로 이루어진다.

 

또한 var는 언제 어디서든 재선언이 가능하지만, const와 let은 재선언이 불가능하다.

function r (a) {

console.log(a);

let a = 'b';

console.log(a);
}
// Uncaught SyntaxError: Identifier 'a' has already been declared

 

 

2-2. class 구문

 

class 또한 선언 이전에 인스턴스를 생성하거나 참조할 수 없다.

 

var juj = new Person('juj');

class Person {
 constructor (name) {
   this.name = name;
 }
}
// Uncaught ReferenceError: Person is not defined

 

2-3. super()

 

만약 부모 클래스를 extend 했다면 constructor() 내부에서 super()를 호출하기 전까지는 this를 사용할 수 없다.

 

TDZ는 인스턴스를 초기화하기 위해 부모 클래스의 생성자를 호출할 것을 제안한다. 부모 클래스의 생성자를 호출하고 인스턴스가 준비되면 자식 클래스에서 this 값을 변경할 수 있다.
class Person {
 constructor (name) {
   this.name = name;
 }
}

class Developer extends Person {
  constructor (name, career) {
    this.career = career;
    super(name);
  }
}

var juj = new Developer('juj', 2);
// Uncaught ReferenceError: Must call super constructor in derived class 
// before accessing 'this' or returning from derived constructor

 

 

2-4. 기본 함수 매개변수

 

지금까지 함수 매개변수는 함수의 지역변수로 간주되어 LexicalEnvironment에 저장된다고 생각했는데 본래는 전역과 함수 스코프에 중간에 위치해 있다.

 

따라서 선언 이전에 매개변수로 이를 사용하게 되면 참조에러가 발생한다.

const a = 2;
function square(a = a) {
  return a * a;
}

square();

매개변수를 사용할 때에는 반드시 선언과 초기화가 이루어진 다음에 사용되어야 한다.

 

따라서 식별자 이름을 바꾸거나 초기화를 한 후에 사용할 수 있다.

const b = 2;
function square(a = b) {
  return a * a;
}

square(); // 정상

function sqrt(a = 1) {
  return Math.sqrt(a);
}

sqrt(); // 정상
기본 매개변수는 선언 및 초기화 다음에 사용되어야 한다.

 

3. 호이스팅되는 구문

 

TDZ에 영향을 받지않는 구문들은 함수 호출 시 실행 컨텍스트의 LexicalEnvironment에 매개변수, 식별자, 지역 변수들의 정보가 미리 저장되는데 이를 호이스팅이라고 한다.

 

자세한 정보는 이전 글에 자세히 기록하였다.

 

호이스팅이 되는 구문은 다음이 있다.

 

3-1. var로 선언한 지역변수

 

var 변수는 선언과 동시에 undefined로 초기화되고, 호이스팅에 의해 할당 부분만 본 위치에서 실행하고, 선언문은 맨 위로 호이스팅 된다.

 

따라서 선언 이전에 var 변수를 참조하는 것이 가능하다.

 

3-2. 함수 선언문

 

function 으로 시작하는 함수 선언문 또한 호이스팅되어 선언 이전에 호출이 가능하다.

 

3-3. import 구문

 

잘 몰랐던 사실인데, import 구문 역시 호이스팅이 되어 import된 위치와 상관없이 어느 위치에서나 임포트한 의존성 모듈을 사용할 수 있다. 그러나 import 구문을 어느 위치에서나 사용하는 것은 안티 패턴이기 때문에 오해가 없도록 파일의 맨 위에서 작성하는 것이 옳다.

 

ES6 모듈을 사용하면 모든 의존성 모듈은 코드가 실행되기 전에 로드된다.

참고. 안티패턴

 

안티 패턴

안티 패턴이란 습관적으로 많이 사용하는 패턴이지만 성능, 디버깅, 유지보수, 가독성 측면에서 부정적인 영향을 줄 수 있어 지양하는 패턴이다. 이 문서는 실수하기 쉬운 안티 패턴을 사례별로 설명하고 개선 방법을 가이드한다.

ui.toast.com

 

4. typeof 동작

 

typeof 는 단순히 변수의 타입을 확인하는 용도 뿐만 아니라 현재 스코프 안에 선언되었는지를 확인할 때에도 유용하다.

 

선언되지 않은 변수에 typeof를 적용하면 undefined가 발생하나, TDZ에 영향을 받는 변수가 선언되기 이전에 typeof를 사용하면 참조 에러가 발생한다.

 

typeof NotDefined; // undefined

typeof a; // Reference Error

let a = 2;

 

5. TDZ가 영향을 주는 스코프

 

TDZ는 변수를 선언한 스코프 내에서만 영향을 미친다. 즉 TDZ는 내부 스코프에서만 영향을 미친다.

 

TDZ은 선언문이 존재하는 스코프 범위 안에서 변수에 영향을 준다.
function foo (){
  console.log(typeof val); // undefined
  
  function bar () {
    console.log(typeof val); // ReferenceError
    let val = 2;
  }

  if (1) { // ReferenceError
    console.log(b);
    const b = 2;
  }

  bar();
}

foo();

 

6. 결론

 

TDZ의 영향을 받는 let, const, class, super(), 매개변수는 선언 이전에 참조하는 것을 금지하고 있다.

 

반면, var, function, import는 호이스팅 되어 TDZ의 영향을 받지 않는다. 따라서 어느 위치에서나 참조할 수 있다.

 

개발자들끼리 협업할 때 서로 오해가 없도록 변수와 함수를 사용하기 위해서 되도록이면 var 변수 선언을 피하고 함수 선언문 또한 함수 표현식으로 작성하고, import 구문은 파일의 맨 앞에 작성하는 것이 좋다.