호이스팅과 TDZ는 무엇이고 어떤 연관이 있을까요?

TheJang·2021년 6월 13일
4

JavaScript

목록 보기
2/2
post-thumbnail

📌 호이스팅 ?

함수 안에 있는 선언들을 모두 끌어 올려서 해당 함수 유효 스코프의 최상단에 선언 하는 것을 말합니다.

호이스팅의 대상

자바스크립트는 ES6에서 도입된 let, const를 포함하여 모든 선언(var, let, const, function, function*, class)을 호이스팅합니다. 호이스팅(Hoisting)이란, var 선언문이나 function 선언문 등을 해당 스코프의 선두로 옮긴 것처럼 동작하는 특성을 말합니다.

let/const 변수 선언과 함수 표현식 에서는 호이스팅이 발생하지 않습니다.

  foo();
  foo2();

  function foo() { // 함수선언문
          console.log("hello");
  }
  var foo2 = function() { // 함수표현식
          console.log("hello2");
  }

📌 함수 선언문과 함수 표현식에서 호이스팅

✅ 함수 선언문에서의 호이스팅

  • 함수 선언문은 코드를 구현한 위치와 관계없이 자바스크립트의 특징인 호이스팅에 따라 브라우저가 자바스크립트를 해석 할 때 맨위로 끌어 올려집니다.
/* 정상 출력 */
function printName(firstname) { // 함수선언문 
    var result = inner(); // "선언 및 할당"
    console.log(typeof inner); // > "function"
    console.log("name is " + result); // > "name is inner value"

    function inner() { // 함수선언문 
        return "inner value";
    }
}

printName(); // 함수 호출 

✅ 함수 표현식에서의 호이스팅

  • 함수 표현식은 함수 선언문과 달리 선언과 호출 순서에 따라서 정상적으로 함수가 실행되지 않을 수 있습니다.
    * 함수 표현식에서는 선언과 할당의 분리가 생깁니다.
/* 오류 */
 function printName(firstname) { // 함수선언문
     console.log(inner); // > "undefined": 선언은 되어 있지만 값이 할당되어있지 않은 경우
     var result = inner(); // ERROR!!
     console.log("name is " + result);

     var inner = function() { // 함수표현식 
         return "inner value";
     }
 }
printName(); // > TypeError: inner is not a function

호이스팅은 함수 선언문과 함수 표현식에서 서로 다르게 동작하기 때문에 주의해야 합니다. 변수에 할당된 함수 표현식은 끌어 올려지지 않기 때문에 이때는 변수의 스코프 규칙을 그대로 따릅니다.

Q. printName에서 “inner is not defined” 이라고 오류가 나오지 않고, “inner is not a function”이라는 TypeError가 나오는 이유?

A. printName이 실행되는 순간 (Hoisting에 의해) inner는 ‘undefined’으로 지정되기 때문
inner가 undefined라는 것은 즉, 아직은 함수로 인식이 되지 않고 있다는 것을 의미합니다.

💡 함수 표현식의 선언이 호출보다 아래에 있는 경우

 /* 오류 */
 function printName(firstname) { // 함수선언문
     console.log(inner); // ERROR!!
     let result = inner();  
     console.log("name is " + result);

     let inner = function() { // 함수표현식 
         return "inner value";
     }
 }

printName(); // > ReferenceError: inner is not defined

console.log(inner);에서 inner에 대한 선언이 되어있지 않기 때문에 inner is not defined 오류가 발생합니다.

✅ 호이스팅 우선순위

💡 같은 이름의 var 변수 선언과 함수 선언에서의 호이스팅

  • 변수 선언이 함수 선언보다 위로 끌어 올려집니다.
  var myName = "hi";

  function myName() {
      console.log("yuddomack");
  }
  function yourName() {
      console.log("everyone");
  }

  var yourName = "bye";

  console.log(typeof myName);
  console.log(typeof yourName);
  /** --- JS Parser 내부의 호이스팅(Hoisting)의 결과 --- */
  // 1. [Hoisting] 변수값 선언 
  var myName; 
  var yourName; 

  // 2. [Hoisting] 함수선언문
  function myName() {
      console.log("yuddomack");
  }
  function yourName() {
      console.log("everyone");
  }

  // 3. 변수값 할당
  myName = "hi";
  yourName = "bye";

  console.log(typeof myName); // > "string"
  console.log(typeof yourName); // > "string"

💡 값이 할당되어 있지 않은 변수와 값이 할당되어 있는 변수에서의 호이스팅

  var myName = "Heee"; // 값 할당 
  var yourName; // 값 할당 X

  function myName() { // 같은 이름의 함수 선언
      console.log("myName Function");
  }
  function yourName() { // 같은 이름의 함수 선언
      console.log("yourName Function");
  }

  console.log(typeof myName); // > "string"
  console.log(typeof yourName); // > "function"
  • 값이 할당되어 있지 않은 변수의 경우, 함수 선언문이 변수를 덮어 씁니다.
  • 값이 할당되어 있는 변수의 경우, 변수가 함수 선언문을 덮어 씁니다.

❗️ 호이스팅 사용시 주의할 점

  • 코드의 가독성과 유지 보수를 위해 호이스팅이 일어나지 않도록 합니다.
    * 호이스팅을 제대로 모르더라도 함수와 변수를 가급적 코드 상단부에서 선언하면, 호이스팅으로 인한 스코프 꼬임 현상은 방지할 수 있습니다.
  • var를 쓰면 혼란스럽고 쓸모없는 코드가 생길 수 있습니다. 그럼 왜 var와 호이스팅을 이해해야 할까요?
    * ES6를 어디서는 쓸 수 있으려면 아직 시간이 더 필요합니다. 그렇지 때문에 ES5로 트랜스 커파일을 해야합니다.
    • 위의 이유로 아직은 var가 어떻게 동작하는 지 이해하고 있어야 합니다.

📌 Temporal Dead Zone(TDZ)

Temporal Dead Zone(TDZ)이란?

선언 전에 변수를 사용하는 것을 비 허용하는 개념상의 공간입니다.

const 변수 선언 부터 시작해봅시다. 변수를 선언하고, 초기화 하면 변수에 접근 할 수 있습니다. 예상대로 동작합니다.

const white = '#FFFFFF';

white; // => '#FFFFFF'

이번에는 선언 전에 white 변수에 접근해봅시다.

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

white; // => '#FFFFFF'

const white ='#FFFFFF' 구문 전 줄 까지, white 변수는 TDZ에 있습니다.

TDZ에 있는 white 변수에 접근하게 되면, ReferenceError: Cannot access 'white' before initialization 자바스크립트 에러가 발생합니다.

📌 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;

✅ 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()

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 값을 변경할 수 있습니다.

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

// Works!
const myCar = new MuscleCar('blue', '300HP');
myCar.power; // => '300HP'

✅ 기본 함수 매개변수(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 모듈 또한 호이스팅이 됩니다.

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는 선언문이 존재하는 스코프 범위 안에서 변수에 영향을 줍니다.

💡 정리

호이스팅은 함수 안에 있는 선언들을 모두 끌어 올려 해당 함수 유효 범위의 최상단에 선언하는 것을 말합니다.

TDZ는 const, let, class는 TDZ에 영향을 받습니다. 즉 const, let, class는 선언 전에 변수를 사용하는것을 허용하지 않습니다.

반대로 var, function, import의 선언은 TDZ의 영향을 받지 않습니다. 특히 var 변수는 선언 전에도 사용할 수 있는 점에서 var 변수 사용을 피해 예기치 못한 오류를 방지하는 것이 좋습니다.

변수가 먼저 선언이 된 경우, 초기화에 따라서 TDZ가 생깁니다. 특히 let,const와 var는 초기화 시점이 다릅니다. var는 암묵적으로 undefined로 초기화 된 상태에서 자바스크립트 코드를 읽기 때문에, TDZ에서 에러가 나지 않습니다.

TDZ를 통해 스코프와, 호이스팅을 살펴보고 어떤식으로 TDZ와 호이스팅이 연관되어 있는지 알아 볼 수 있는 좋은 개념이였습니다.

🐰 Reference

profile
어제보다 오늘 더 노력하는 프론트엔드 개발자

0개의 댓글