본문 바로가기

JavaScript

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

원문: https://dmitripavlutin.com/javascript-variables-and-temporal-dead-zone/

 

Don't Use JavaScript Variables Without Knowing Temporal Dead Zone

Temporal Dead Zone forbids the access of variables and classes before declaration in JavaScript.

dmitripavlutin.com

 

아래 코드 스니펫에서 에러가 발생할까? 첫 번째 코드는 인스턴스를 생성한 다음 클래스를 선언한다. 

 

new Car('red'); // Does it work?

class Car {
  constructor(color) {
    this.color = color;
  }
}

 

두 번째 코드는 함수를 실행한 다음 함수를 선언한다.

 

greet('World'); // Does it work?

function greet(who) {
  return `Hello, ${who}!`;
}

 

정답은 다음과 같다. 첫 번째 코드에서는 ReferenceError가 발생하고, 두 번째 코드는 잘 동작한다. 

TDZ는 let, const, class 구문의 유효성을 관리한다. 자바스크립트에서 변수가 동작하는 방식은 중요하다.

TDZ은 무언인가?

const 변수 선언부터 시작해보자. 변수를 선언하고 초기화하면 변수에 접근할 수 있다. 예상대로 동작한다. 

 

const white = '#FFFFFF';

white; // => '#FFFFFF'

 

이번에는 선언 전에 white 변수에 접근해보도록 하겠다.

 

white; // throws `ReferenceError`
const white = '#FFFFFF';

white

 

const white = '#ffffff' 구문 전 줄까지, white 변수는 TDZ에 있다. TDZ에 있는 white 변수에 접근하게 되면, ReferenceError: Cannot access 'white' before initialization 자바스크립트 에러가 발생한다.

 

TDZ에 영향을 받는 구문

TDZ에 영향을 받는 구문들을 살펴보자.

const 변수

이전에 보았듯이 const 변수는 선언 및 초기화 전 줄까지 TDZ에 있다.

 

// Does not work!
pi; // throws `ReferenceError`
const pi = 3.14;

 

const 변수는 선언한 후에 사용해야 한다.

 

const pi = 3.14;

// Works!
pi; // => 3.14

 

let 변수

let도 선언 전 줄까지 TDZ의 영향을 받는다. 

 

// Does not work!
count; // throws `ReferenceError`
let count;

count = 10;

 

다시, let 변수도 선언 이후에 사용해야 한다.

 

let count;

// Works!
count; // => undefined
count = 10;

// Works!
count; // => 10

class 구문

머리말 부분에서 보았듯이, 선언 전에는 class를 사용할 수 없다. 

 

// Does not work!
const myNissan = new Car('red'); // throws `ReferenceError`

class Car {
  constructor(color) {
    this.color = color;
  }
}

 

이 예제가 동작하려면, 클래스를 선언한 후에 사용하도록 수정해야 합니다. 

 

class Car {
  constructor(color) {
    this.color = color;
  }
}

// Works!
const myNissan = new Car('red');
myNissan.color; // => 'red'

 

constructor() 내부의 super()

부모 클래스를 상속받았다면 생성자 안에서 super()를 호출하기 전까지 this 바인딩은 TDZ에 있다.

 

class MuscleCar extends Car {
  constructor(color, power) {
    this.power = power;
    super(color);
  }
}

// Does not work!
const myCar = new MuscleCar(‘blue’, ‘300HP’); // `ReferenceError`

 

이 코드를 보면 constructor() 안에서 super()가 호출되기 전까지 this를 사용할 수 없다. TDZ는 인스턴스를 초기화하기 위해 부모 클래스의 생성자를 호출할 것을 제안한다. 부모 클래스의 생성자를 호출하고 인스턴스가 준비되면 자식 클래스에서 this값을 변경할 수 있다.

 

기본 함수 매개변수(Default Function Parameter)

기본 매개변수는 글로벌과 함수 스코프 사이의 중간 스코프에 위치한다. 기본 매개변수 또한 TDZ 제한이 있다.

 

const a = 2;
function square(a = a) {
  return a * a;
}
// Does not work!
square(); // throws `ReferenceError`

 

기본 매개변수 a는 선언전에 a = a 표현식의 오른쪽에서 사용되었다. a에서 참조 에러가 발생한다. 기본 매개변수는 선언 및 초기화 다음에 사용되어야 한다. 이 경우 init과 같은 다른 변수로 선언하여 사용한다. 

 

const init = 2;
function square(a = init) {
  return a * a;
}
// Works!
square(); // => 4

 

var, function, import 구문

위에서 설명한 것들과 반대로 var, function 선언은 TDZ에 영향을 받지 않는다. 이것들은 현재 스코프에서 호이스팅된다. var 변수는 선언하기 전에 접근하면, undefined를 얻게 된다.

 

// Works, but don't do this!
value; // => undefined
var value;

 

그러나 함수는 선언된 위치와 상관없이 동일하게 호출된다.

 

// Works!
greet('World'); // => 'Hello, World!'
function greet(who) {
  return `Hello, ${who}!`;
}

// Works!
greet('Earth'); // => 'Hello, Earth!'

 

당신은 함수 구현보다 호출에 더 관심이 있기 때문에 종종 함수 선언 전에 호출하게 된다. 함수 선언 전에 호출해도 에러가 발생하지 않는 이유는 호이스팅 때문이다. 흥미로운 점으로 import 모듈 역시 호이스팅 된다.

 

// Works!
myFunction();
import { myFunction } from './myModule';

 

import  구문이 호이스팅되기 때문에, 자바스크립트 파일 시작 부분에서 디펜던시 모듈을 가져오는 것이 좋다.

TDZ에서 typeof 연산자의 동작

typeof 연산자는 변수가 현재 스코프 안에 선언되었는지 확인할 때 유용하다. 예를 들어서, notDefined 변수는 선언되지 않았다. 이 변수에 typeof 연산자를 적용하면 에러가 발생한다.

 

typeof notDefined; // => 'undefined'

 

변수가 선언되지 않았기 때문에, typeof notDefined는 undefined로 평가한다. 그러나 TDZ의 변수에서 typeof 연산자를 사용하면 다르게 동작한다. 다음과 같은 경우에 에러가 발생한다.

 

typeof variable; // throws `ReferenceError`
let variable;

 

현재 스코프 안에서 TDZ 동작

TDZ은 선언문이 존재하는 스코프 범위 안에서 변수에 영향을 준다. 

 

 

function doSomething(someVal) {
  // Function scope
  typeof variable; // => undefined
  if (someVal) {
    // Inner block scope
    typeof variable; // throws `ReferenceError`
    let variable;
  }
}
doSomething(true);

 

위 코드는 두개의 스코프를 가진다. 1. 함수 스코프, 2. let 변수가 선언된 내부 블록 스코프

 

함수 스코프에서 typeof variable는 undefined로 평가된다. 여기서는 let variable 구문의 TDZ에 영향을 주지 않는다. 

 

typeof variable 구문의 내부 스코프에서는 선언 전에 변수를 사용하면 ReferenceError: Cannot access 'variable' before initialization에러가 발생한다. TDZ는 내부 스코프에서만 존재한다. 

 

결론

TDZ는 const, let, class 구문의 유효성에 영향을 미치는 중요한 개념이다. TDZ는 선언 전에 변수를 사용하는 것을 허용하지 않는다. 반대로 var 변수는 선언 전에도 사용할 수 있기 때문에 var 사용은 피하는 것이 좋다. TDZ는 언어 스펙에 맞도록 좋은 코딩 습관을 만들어주기 때문에 좋다고 생각한다.