조각조각 - 호이스팅, 일시적 사각지대(TDZ)

eocode·2024년 3월 4일
0
post-thumbnail

목차

  1. 호이스팅 (Hoistring)
    1.1. 호이스팅이란?
    1.2. 호이스팅 발생 이유
    1.3. 선언 이전 참조 가능 여부
    1.4. 함수 호이스팅
  2. 일시적 사각 지대 (TDZ)
    2.1. const 타입 변수 TDZ
    2.2. let 타입 변수 TDZ
  3. 참고 자료

1. 호이스팅

🔗 개념 정리 1 - 실행 컨텍스트

이전 글에서 자바스크립트 엔진의 평가 단계에서 호이스팅이 발생한다고 언급하였습니다. 이번 글에선 이 호이스팅이 무엇인지 그리고 특징과 왜 발생하는지에 대해 알아보겠습니다.

1-1. 호이스팅이란?

호이스팅이란 자바스크립트 엔진이 코드를 실행하기 전에 선언문들이 현재 스코프의 최상단에 올라온 것처럼 보이는 현상을 말합니다.

1-2. 호이스팅 발생 이유

그렇다면 호이스팅이 발생하는 이유는 무엇일까요? 이것을 알기 위해 자바스크립트 엔진이 어떻게 동작하는지 알아야 합니다.

이전 게시글에서 보았듯이 자바스크립트 엔진은 코드를 실행할 때 평가 단계와 실행 단계를 거칩니다. 평가 단계에선 선언문들만 따로 처리하고 실행 단계에서 나머지 코드들을 처리합니다.

호이스팅이 발생하는 이유는 바로 이 평가 단계에 있습니다. 자바스크립트 엔진은 평가 단계에서 선언문만 처리하며 실행 컨텍스트를 생성합니다. 이때 선언된 변수들이 환경 레코드에 저장되는데 아래 변수 선언 과정을 거칩니다.

자바스크립트 엔진의 변수 선언 과정은 아래 2단계에 거쳐 진행됩니다.

  • 선언 단계 : 환경 레코드에 식별자(변수 이름) 등록
  • 초기화 단계 : 값을 저장하기 위한 메모리 공간을 확보하고 var 타입 변수의 경우 암묵적으로 undefined를 할당해 초기화한다.

모든 변수는 변수 선언 과정을 거치며 선언 단계를 진행합니다. 즉 아직 평가 단계만 완료한 상태인데도 마지 선언문 코드가 실행된 듯이 환경 레코드에 식별자가 존재하게 됩니다. 그렇기 때문에 자바스크립트 엔진이 실행 단계를 진행할 때 마치 선언문들이 현재 스코프 최상위에 올라온것처럼 보이게됩니다.

정리하자면 호이스팅은 자바스크립트 엔진의 평가 단계 그 안에서도 변수 선언 단계 중 식별자를 등록하는 선언 단계 때문에 발생하게 됩니다.

여기서 주의해야할 점은 올라간것 처럼 보이는 것과 변수 선언전에 참조 가능한것은 별개입니다. 변수 선언전에 참조가 불가능 하더라도 평가 단계를 거쳐 스코프 최상단에 올라간 것 처럼 보이면 호이스팅이라 할 수 있습니다. 즉, 호이스팅은 단순히 스코프 최상단에 올라간 것 처럼 보이는 현상입니다. 변수 선언전 사용 가능, 불가능의 특징은 변수 타입이나 함수 선언문, 함수 표현식의 차이로 발생합니다. 호이스팅 때문이 아닙니다. 이 특징은 아래에서 자세히 살펴보겠습니다.

1-3. 선언 이전 참조 가능 여부

호이스팅은 그저 선언문이 스코프 최상단에 올라간것 처럼 보이는 현상입니다. 선언문 이전 변수 참조 가능 여부는 호이스팅이 아닌 변수 타입, 함수 타입에 따라 달라집니다. 간단히 말하자면 선언문 이전 참조가 가능한것은 var 타입 변수와 함수 선언문이며 선언문 이전 참조가 불가능 한것은 es6 이후 추가된 let, const 타입 변수입니다. (함수 표현식은 할당된 변수의 타입에 따라 달라집니다.)

var, let, const 변수 타입

자바스크립트 엔진이 코드를 실행하며 var, let, const 변수가 어떻게 환경 레코드에 저장되는지 살펴보겠습니다.

var varTest;
const constTest = 'const';
let letTest;
varTest = 'var';
letTest = 'let';

생성 단계 후

varletconst1

자바스크립트 엔진이 평가 단계를 진행하면서 선언문만 실행합니다. 이때 var 타입 변수는 식별자를 환경 레코드에 등록하고 더해서 값을 undefined로 초기화 합니다. 하지만 const, let 타입 변수는 식별자가 환경 레코드에 등록되지만 초기화는 진행되지 않습니다.

따라서 자바스크립트 엔진이 평가 단계를 진행할때 최상단의 위치에서 varTest 변수를 참조 할 수 있습니다. 하지만 초기화 되지 않아 아무값도 가지고 있지 않은 constTest, letTest 변수는 참조가 불가능합니다.

실행 단계 후

varletconst2 실행 단계가 진행되며 선언문 이외 코드가 실행됩니다. 이때 varTest는 'var'를 constTest는 'const'를 할당받고 letTest는 'let'을 할당 받습니다.

정리하자면 선언 단계에서 'undefined'로 초기화가 이루어지는 var 타입 변수는 변수 선언 이전에 참조가 가능하고 초기화가 이루어지지 않는 let, const 타입 변수는 변수 선언 이전 참조가 불가능합니다.

var, let, const 정리

varletconst
-ES6부터 등장ES6부터 등장
변수변수상수
호이스팅 O호이스팅 O호이스팅 O
생성단계
선언 O
생성단계
선언 O
생성단계
선언 O
생성단계
초기화 O (undefined)
생성단계
초기화 X
생성단계
초기화 X
TDZ 존재 XTDZ 존재 OTDZ 존재 O
재선언 O재선언 X재선언 X

1-4. 함수 호이스팅

변수뿐만 아니라 함수에서도 호이스팅이 발생합니다. 앞서서 변수의 경우 자바스크립트 엔진의 평가 과정에서 식별자가 등록되면 호이스팅이 발생한다고 했습니다. 함수 또한 평가 과정에서 식별자가 등록되기 때문에 호이스팅이 발생합니다. 즉 선언 이전 사용 여부과 관계 없이 선언문이 스코프 최상단으로 올라간것처럼 보입니다. 모든 함수에게 호이스팅이 발생합니다.

선언 이전 함수 참조 및 사용 여부는 함수 선언문, 함수 표현식, 변수 타입에 따라 달라집니다. 우선 함수 선언문과 함수 표현식이 무엇인지 알아보겠습니다.

함수 선언문, 함수 표현식

함수 선언문과 함수 표현식은 자바스크립트에서 함수를 정의하는 두 가지 방법입니다.

함수 선언문

function func(param1, param2, ...) {
  // 함수 몸체
}

함수 선언문은 위 코드와 같은 형태로 작성합니다.

함수 선언문은 자바스크립트 엔진 평가 단계에서 온전한 함수 형태로 초기화 됩니다. 즉 실행 컨텍스트가 생성되며 환경 레코드에 함수 식별자와 함수 코드가 등록됩니다. 그렇기 때문에 선언문 이전에 참조가 가능하며 함수 실행도 가능합니다.

함수 표현식

// 화살표 함수 사용 함수 표현식
const func2 = (param1, param2, ...) => {
	console.log("함수 표현식(화살표 함수)");
};

// 익명 함수 사용 함수 표현식
const func2 = function (param1, param2, ...) {
	console.log("함수 표현식(익명 함수)");
};

함수 표현식은 다음과 같은 형태로 작성합니다.

함수 표현식은 변수에 함수 할당하는 형태입니다. 따라서 변수의 스코프 규칙을 따르게 됩니다. var 타입 변수에 할당된 경우 var 타입 변수와 마찬가지로 생성 단계에서 undefined로 초기화 됩니다. 따라서 함수 선언 이전 참조가 가능합니다. 하지만 undefined로 초기화 되어있어 함수 실행은 불가능 합니다. (var 표현식 함수를 console.log로 찍어보면 undefined가 나옵니다. ) let, const 타입 함수 표현식도 let, const 타입 변수와 마찬가지로 생성 단계에서 초기화가 이루어지지 않습니다. 따라서 함수 선언 이전 참조가 불가능합니다. 참조가 불가능 하기 때문에 실행도 불가능합니다.

자바스크립트 엔진의 생성과정을 거친 후 함수 선언문, 함수 표현식(var, const, let)은 아래와 같아집니다.

functionhoisting

함수 호이스팅 정리

정리하자면 함수 선언 이전 참조와 실행 모두 가능한것은 함수 선언문, 참조만 가능한 것은 var 타입 함수 표현식, 참조, 실행 모두 불가능 한것은 let, const 타입 함수 표현식입니다.

-호이스팅참조실행재선언
함수 선언문OOOO
함수 표현식(var)OOXO
함수 표현식(const)OXXX
함수 표현식(let)OXXX

변수든 함수든 선언 이전에 사용할 수 있다면 많은 오류가 발생할 수 있습니다. 그렇기 때문에 es6에서 탄생한 것이 let, const 타입 변수와 이를 활용한 함수 표현식입니다.

함수 선언문의 경우 선언문 이전에 참조도 가능하고 실행도 가능합니다. 또 재선언도 가능합니다. 어디에선가 함수 선언문이 선언되었다면 어디 서든 함수 참조가 가능하고 함수 실행도 가능하다는 뜻 입니다. 또 함수 재선언도 어디에서든 가능합니다. 코드가 복잡한 경우 나도 모르는 곳에서 함수가 변경될 수 있고 실행 될 수 있어 사용성이 좋지 않습니다.

반면에 함수 표현식을 사용하면 변수 스코프 규칙을 따르기 때문에 let, const 변수를 사용하면 선언문 이전 실행을 막을 수 있습니다. 또 함수 재선언도 방지할 수 있습니다. 따라서 중간에 어디에서든 변화가 일어나는 것을 방지 할수 있어 예기치 못한 오류를 막을 수 있습니다. 따라서 함수 선언문보다 함수 표현식(let, const)를 사용하는 것이 좋다고 생각됩니다.

🚨🚨🚨 함수 표현식이더라도 var 타입 변수를 사용하면 선언 이전 사용과 재선언이 가능하니 주의해야 합니다.

2. 일시적 사각 지대 (TDZ)

일시적 사각지대(TDZ(Temporal Dead Zone))란 let이나 const로 선언한 변수 스코프의 시작 지점부터 초기화 시작 지점까지를 뜻합니다. 초기화 단계가 실행되기 이전에 변수에 접근하려고 하면 참조 에러가 발생하기 때문에 TDZ에서 변수에 접근할 수 없습니다. 다시 말하자면 let과 const 타입 변수의 경우 실행 단계(런타임)에서 변수 선언문 코드가 실행 되어야 변수 초기화가 되고 참조가 가능해집니다. 따라서 초기화가 진행되기 이전 스코프 시작 부분부터 변수 선언문, 참조가 불가능한 부분을 TDZ라 합니다.

간단히 표현하자면 변수 초기화가 진행되지 않아 접근 불가능한 죽은 영역입니다.

2-1. const 타입 변수 TDZ

console.log(varType); // 참조 에러 //A
//B
//C
const constType = "const"; //D
//E
//F

스코프 시작지점인 A라인부터 변수 선언문 이전 C라인까지 TDZ입니다. 따라서 A라인에서 참조 에러가 발생합니다. D라인 변수 선언문에서 "const"로 초기화가 진행 되었기 때문에 E라인 이후에 변수 참조가 가능합니다.

2-2. let 타입 변수 TDZ

선언과 동시에 값 할당된 경우

console.log(varType); // 참조 에러 //A
//B
//C
let letType = "let" //D
//E
//F

스코프 시작지점인 A라인부터 변수 선언문 이전 C라인까지 TDZ입니다. 따라서 A라인에서 참조 에러가 발생합니다. D라인 변수 선언문에서 "let"으로 초기화가 진행 되었기 때문에 E라인 이후에 변수 참조가 가능합니다.

선언과 값 할당이 분리된 경우

console.log(varType); // 참조 에러 //A
//B
//C
let letType; //D
console.log(varType) //'undefined' //E
letType = "let"; //F

스코프 시작지점인 A라인부터 변수 선언문 이전 C라인까지 TDZ입니다. 따라서 A라인에서 참조 에러가 발생합니다. D라인 변수 선언문에서 값이 할당되지 않지만 'undefined'로 초기화가 진행 되었기 때문에 E라인에서 참조가 가능합니다. 이후 F라인에서 제대로 값을 할당 받게되어 F라인 이후 부터 제대로 'let'이 참조됩니다.

각 라인을 살펴보면 아래와 같습니다.

  • A라인
    - 생성단계 letType 선언 O
    - 생성단계 letType 초기화 X
    - letType 참조 불가능
    - TDZ O
  • B라인
    - 생성단계 letType 선언 O
    - 생성단계 letType 초기화 X
    - letType 참조 불가능
    - TDZ O
  • C라인
    - 생성단계 letType 선언 O
    - 생성단계 letType 초기화 X
    - letType 참조 불가능
    - TDZ O
  • D라인
    - 생성 단계 letType 선언 O
    - 생성 단계 letType 초기화 X
    - 실행 단계 letType undefined 값 초기화
    - TDZ X
  • E라인
    - 생성 단계 letType 선언 O
    - 생성 단계 letType 초기화 X
    - 실행 단계 letType undefined 값 초기화된 상태
    - letType 참조 가능
    - TDZ X
  • F라인
    - 생성 단계 letType 선언 O
    - 생성 단계 letType 초기화 X
    - 실행 단계 letType "let" 값 할당
    - letType 참조 가능
    - TDZ X

let과 const 타입을 사용하면 TDZ가 형성되어 변수 선언 이전에 접근하지 못하는 보편적인 프로그래밍 언어들과 같게 동작합니다. 이 덕분에 불필요한 오류를 방지할 수 있습니다. 따라서 var 보다 let, const 사용하는것이 좋습니다.

참고 자료

profile
프론트엔드 개발자

0개의 댓글