Hoisting과 TDZ

이진혁·2022년 9월 20일
0
post-thumbnail

사전지식

변수 생성 단계

자바스크립트는 3가지 단계를 거쳐 변수를 생성한다.

  • 선언 단계(Declaration Phase) : 변수 객체를 실행 컨텍스트에 등록한다.
  • 초기화 단계(Initialization Phase): 등록된 변수의 메모리를 확보한다. 이 단계에서 변수는 undefined로 초기화된다.
  • 할당 단계(Assignment Phase): 초기화된 변수에 실제 값을 할당한다.

let, const를 사용해 변수를 생성할 시에는 세 단계가 각각 따로따로 이루어지고,

var 을 사용해 변수를 생성할 시에는 선언 단계와 초기화 단계가 한번에 이루어지며,

함수 선언문 을 사용할 시에는 세 단계가 한 번에 이루어진다.

유효 범위(variable scope)

변수에 접근할 수 있는 범위를 말한다.

  • 블록 스코프: 중괄호 {}로 감싸진 범위.
  • 함수 스코프: function(){}에서 중괄호 {} 내부의 범위. 즉 함수 코드 블록 내부의 범위.

자바스크립트는 기본적으로 함수 스코프 를 따르며, var로 변수를 생성할 시 함수 스코프 내에서 유효하다.

그러나 let, const로 변수를 생성할 시, 블록 스코프 내에서 유효하다.

호이스팅이란?

MDN Web Docs에 따르면, JavaScript에서 호이스팅(hoisting)이란, 인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미한다.

이는 코드 실행 전에 변수와 함수 선언이 메모리에 할당되는 것을 의미하는데 조금 더 자세히 알아보자.

프로그램은 작성한 순서에 따라 윗줄부터 차례대로 실행된다.

그러나 변수 선언은 이 원칙을 따르지 않는다.

아래의 코드를 살펴보자.

console.log(x); // undefined
var x;

이 코드에서 첫번째 줄은 아직 변수 x가 선언되지 않았기 때문에 오류가 발생할 것 같지만, undefined가 출력된다.

오류가 발생하지 않고 undefined가 출력되는 이유는 호이스팅으로 인해 변수 x가 실행 컨텍스트 안에서 식별되고 선언됨과 동시에 초기화 단계를 거쳐 등록된 변수의 메모리를 확보하기 위해 undefined가 할당된 것이다.

반면 선언과 동시에 할당하는 코드는 끌어올리지 않는다.

아래의 코드를 살펴보자.

console.log(x); // undefined

var x = 5;

console.log(x); /// 5

아래의 첫번째 줄은 undefined 가 출력되고 세번째는 5가 출력된다.

변수 선언부 var x는 끌어올리지만

할당부 x = 5는 끌어올리지 않기 때문이다.

위 코드를 해석해보면

/*자바스크립트 Parser 내부의 호이스팅 결과*/
var x = undefined; // 변수 선언

console.log(x); // undefined

x = 5; // 변수 할당

console.log(x); // 5

이렇게 해석할 수 있겠다.

호이스팅이 발생하는 이유

  • var x = 5;는 하나의 구문처럼 보이지만, 자바스크립트 엔진은 그렇게 보지 않는다.
  • var xx = 5라는 두 개의 구문으로 보고, 첫 번째는 컴파일러 단계에서 처리하고, 두 번째 구문은 실행 단계에서 처리한다.
  • 이 의미는 스코프의 모든 선언문은 실행 전에 먼저 처리된다는 점이다.

호이스팅이 발생하는 이유에 대해 더 자세히 알아보자.

실행 컨텍스트의 Lexical EnvironmentEnvironment Record (환경 레코드)와 Outer LexicalEnvironment Reference (외부 렉시컬 환경 참조 컴포넌트)로 구성된다.

여기서 Environment Record 는 유효 범위(scope) 안의 식별자(identifier)와 해당 식별자에 바인딩된 값(value)을 기록하는 내부 도구다.

여기에 함수 선언, 변수명 등이 담기는데, 컨텍스트 내부 전체를 처음부터 끝까지 훑으면서 순서대로 수집한다.

위 과정에서 변수의 선언문을 모조리 읽으며 '미리' 식별했기 때문에

자바스크립트 엔진은 코드가 실행되기 전에도 이미 해당 환경에 속한 코드 변수명들을 알고 있기 때문에 호이스팅이 일어나는 것이다.

TDZ (Temporal Dead Zone)란?

TDZ(Temporal Dead Zone)란 변수가 선언된 위치에서부터 초기화가 이루어질 때까지 접근할 수 없는 영역을 말한다.

쉽게 말해 TDZ는 일시적인 사각지대로, 변수를 사용하는 것을 비허용하는 개념상의 공간이라고 생각하면 된다.

TDZ에 있는 값에 접근하게 되면 ReferenceError: Cannot access 'xxx' before initialization 에러가 발생한다.

var 와 똑같이 let과 const 역시 호이스팅이 발생하지만 조금 다르다.

선언문을 통해 모든 식별자(변수, 함수, 클래스 등)는 호이스팅된다.

var : 선언 및 초기화 -> 재할당

let, const: 선언 -> TDZ -> 초기화 -> 할당

var의 경우 선언 및 초기화가 동시에 이루어지기 때문에 undefined로 할당되지만,

let과 const는 var와 다르게 초기화되기 전까지 TDZ에 머물러있기 때문에 호이스팅이 발생하지 않는 것처럼 보이며 참조가 불가능하다.

그래서 ReferenceError가 발생하는 것이다.

let, const는 왜 TDZ에 들어갈까?

그럼 var 와는 달리 let 이나 const 은 왜 TDZ에 들어가는 것일까?

아래의 코드를 살펴보자.

// var
var->AllocateTo(VariableLocation::PARAMETER, index);

// let
VariableProxy* proxy =
    DeclareBoundVariable(variable_name, VariableMode::kLet, class_token_pos);
proxy->var()->set_initializer_position(end_pos);

// const
VariableProxy* proxy =
    DeclareBoundVariable(local_name, VariableMode::kConst, pos);
proxy->var()->set_initializer_position(position());

V8 엔진의 코드를 보면 var 는 변수 객체를 생성한 후 AllocateTo 메소드를 통해 바로 메모리에 공간을 할당한다.

const 나 let 은, set_initializer_position 메소드를 통해 해당 코드의 위치를 의미하는 position 값만 정해준다.

선언은 되어 있지만 변수에 값을 담기 위한 메모리에 공간이 확보되지 않은 상태가 되는 것이다.

이 시점에 let, const 키워드로 생성된 변수들이 TDZ에 들어간다.

따라서 TDZ에 있는 변수 객체란, 선언은 되어있지만 아직 초기화되지 않아서 변수에 담길 값을 위한 공간이 메모리에 할당되지 않은 상태임을 알 수 있다.

profile
개발 === 99%의 노력과 1%의 기도

0개의 댓글