[JS] 호이스팅? TDZ? var과 let/const? 확실하게 짚기

이정민 Lee Jeong Min·2021년 6월 9일
5

Javascript

목록 보기
9/10
post-thumbnail

호이스팅이란?

코드에 선언된 변수 및 함수가 코드 상단으로 끌어올려지는 것처럼 동작하는 것을 말합니다.
이는 자바스크립트 파서가 내부적으로 끌어올려서 처리하는 것으로, 실제 메모리에서는 변화가 없습니다!

이러한 호이스팅이 발생하는 이유는 자바스크립트 해석기의 동작 방식 때문입니다.
자바스크립트 코드가 실행되면 파싱을 하게 되는데, 이때 코드를 해석해 전역 컨텍스트에 전역 변수 및 함수를 등록하려하기 때문에 끌어올려지는 현상이 발생하게 됩니다.

변수 호이스팅

console.log(name); // undefined
var name = 'jeongmin';

코드 상으로 보기에는 name 변수의 생성과 초기화가 console.log 이후에 이루어졌으니 에러가 발생할 것 같아 보이지만, undefined로 출력되는 것을 볼 수 있습니다.


위 코드는 아래와 같은 의미를 가집니다.

var name = undefined;
console.log(name); // undefined
name = 'jeongmin';

이러한 현상이 호이스팅으로, 해당 변수가 상단으로 끌어올려져 undefined로 초기값이 할당되는 것입니다.


따라서 변수가 선언되기 전에 변수를 사용할 수도 있습니다.

name = 'jeongmin';
var name;

스코프와 함께 예시를 보도록 합니다.
var는 함수 스코프를 가지므로 즉시 실행함수와 함께 코드를 작성해보겠습니다.

var x = 'outer';

(function() {
  console.log(x); // undefined
  var x = 'inner';
}());

먼저 초기화된 outer가 출력될 것 같지만, undefined가 출력됩니다.
var x가 해당 스코프의 최상단으로 호이스팅되기 때문입니다.


위 코드는 아래와 같은 의미를 가집니다.

var x = 'outer';

(function() {
  var x;
  console.log(x); // undefined
  x = 'inner';
}());

함수 호이스팅

함수의 경우에는 표현식이냐 선언식이냐에 따라 조금 다르게 동작합니다.

var result = sum(1 , 1); // error!
var sum = function(num1, num2){
   return console.log(num1 + num2);
}

위와 같은 표현식의 경우 var sumundefined가 초기값으로 할당되기 때문에 sum is not a function이라는 에러 문구가 뜨게 됩니다.


var result = sum(1, 1); // 2
function sum(num1, num2){
   return console.log(num1+num2);
}

위와 같은 선언식의 경우 함수가 그대로 끌어올려지기 때문에 sum 로직이 잘 실행됩니다.

let과 const는?

console.log(name); // reference error!
const name = 'danmin';

위의 코드는 var와 달리 에러를 발생시킵니다.
이러한 결과는 let/const 선언이 호이스팅을 수행하지 않는 것처럼 보이는데, 그렇지 않습니다.


위의 스코프 예시와 함께 보도록 하겠습니다.

const x = 'outer';
(function() {
  console.log(x); // reference error!
  const x = 'inner';
}());

만약에 호이스팅이 일어나지 않는 것이라면 최상위 스코프의 outer가 출력되어야 하지만, 에러가 발생하게 됩니다.
즉, 참조 에러가 발생하는 것 자체가 호이스팅이 되기 때문입니다.

초기화 전에 접근할 경우 varundefined를 반환하지만, let/const는 에러가 발생하게 됩니다.

TDZ

그렇다면 왜 이러한 에러가 발생하는 것일까요?
바로 Temporal Dead Zone의 영향을 받기 때문입니다.

자바스크립트는 대부분의 언어와 같이 lexical scope를 따르는데, lexical 환경에 포함될 때 변수가 생성되지만 실행되기 전까지는 접근을 막는 개념으로 볼 수 있습니다.

즉, 새로운 범위에 진입할 때마다 지정된 범위에 속한 모든 let/const 바인딩이, 지정된 범위 내부의 코드가 실행되기 전에 일어나게 되는데, (호이스팅)
바인딩이 일어나기 전까지 접근을 막는 것입니다.

이 때 let의 경우 lexical 바인딩 시 초기화 구문이 없으면 undefined가 할당됩니다.
const는 반드시 선언과 동시에 할당을 해주어야 합니다.

Default Parameters

디폴트 파라미터에서도 TDZ가 적용됩니다.

(function(a, b = a) {
  console.log(a); // 1
  console.log(b); // 1
}(1, undefined));

디폴트 파라미터는 왼쪽에서 오른쪽으로 실행되기 때문에, b의 초기화 구문이 a를 읽을 수 있기 때문에 정상적으로 작동합니다.


(function(a = b, b) { // ReferenceError
  /* ... */
}(undefined, 1));

a의 초기화 구문이 b를 읽을 때 b가 TDZ에 있으므로 참조에러가 발생하게 됩니다.

Class

클래스에서도 TDZ가 적용됩니다.

class Person {
  constructor(age, nickname) {
    this.name = 'gildong';
    this.age = age;
    this.nickname = nickname;
  }
}

class Error extends Person {
  constructor(age, nickname) {
    this.age; // reference error!
  }
}

class Success extends Person {
  constructor(age, nickname) {
    super(age, nickname);
    this.name = 'jeongmin';
  }
}

const danmin_1 = new Error(23, 'danmin'); 
const danmin_2 = new Success(23, 'danmin');

상위 생성자를 호출하기 전에 this에 엑세스하려고 하는 하위 클래스의 생성자는 참조에러를 발생시키 됩니다.

하위 클래스의 constructor 내부에서 super()를 호출해야 this에 접근할 수 있게 됩니다.

결론

TDZ는 초기화되지 않은 바인딩에 엑세스할 경우 에러를 발생시키기 때문에 유용합니다.
var의 경우 TDZ가 적용되지도 않을 뿐더러 함수레벨 스코프를 가지기 때문에 디버깅과 가독성이 좋지 않다는 단점이 있습니다.

따라서 let/const로 통일성 있게 코드를 작성하면 좋을 것 같습니다 :)

profile
https://jeong-min.com/ <- 블로그 이전했습니다 :)

1개의 댓글

comment-user-thumbnail
2024년 1월 25일

Their gorgeous companions will make all your secret dreams come true. Enjoy a thrilling rendezvous with a striking brunette or an alluring blonde. Pakistani Delhi Escort Their elite escorts are highly educated, well-traveled, and speak fluent English so you can indulge in flirtatious conversation before getting down to business. Safemeetup’s top priority is your complete satisfaction and confidentiality. Forget your inhibitions and enjoy a romantic interlude you’ll never forget!

답글 달기