일반적으로 ES6에서 도입된 let
, const
는 호이스팅 되지 않는다고 표현한다.
var
로 선언한 변수들은 선언부가 먼저 상위로 끌어올려져 수행된다. 따라서 아래와 같은 현상이 나타난다.
console.log(a); // undefined
var a = 2;
↓
var a; // 변수 a에 대한 선언이 먼저 상위로 끌어올려지고, 값은 아직 할당되지 않았다.
console.log(a); // 그러니까 undefined
a = 2;
그렇지만 let
과 const
는 이런 현상이 발생하지 않는다. 그러면 let
과 const
는 호이스팅 되지 않는걸까?
var name = ‘me’;
{
console.log(name);
let name = ‘you’;
}
위 코드를 보자. 만약 let
이 호이스팅 되지 않는다면, console.log
에는 'me'가 찍혀야 한다. 그런데 실행해보면 아래 에러를 뱉는다.
엄밀하게 말하면 let
과 const
도 호이스팅의 영향을 받지만, var
와는 변수 생성 과정이 다른 것뿐이다.
자바스크립트에서 변수의 생성 과정은 아래 3단계로 진행된다.
- 선언 단계(Declaration phase) : 변수를 실행 컨텍스트의 변수 객체(Variable Object)에 등록한다.
- 초기화 단계(Initialization phase) : 변수 객체(Variable Object)에 등록된 변수를 위한 공간을 메모리에 확보한다. 이 단계에서 변수는 undefined로 초기화된다.
- 할당 단계(Assignment phase) : undefined로 초기화된 변수에 실제 값을 할당한다.
var
키워드로 변수를 생성할 경우, 위의 선언 단계와 초기화 단계가 동시에 진행된다. 즉, 스코프에 변수를 등록(선언 단계)하고 메모리에 변수를 위한 공간을 확보한 후, undefined
를 할당(초기화 단계)한다. 따라서 변수 선언문 이전에 변수에 접근하여도 에러가 발생하지 않는다.
반면, let
은 선언 단계와 초기화 단계가 분리되어 진행된다. 호이스팅되어 선언부는 상위로 끌어올려지지만, 초기화는 let이 실제로 사용되는 부분에서 이뤄진다. 초기화 이전에 변수에 접근하려고 하면 참조 에러(ReferenceError)가 발생한다. 변수를 위한 메모리 공간이 아직 확보되지 않았기 때문이다. const
변수도 let
과 유사하지만 재할당이 불가능하고 선언과 할당이 같이 이루어져야 한다는 차이가 있다.
구글링을 하다보니 어떤 감사한 분이 V8 엔진의 깃허브 레포를 클론받아서 var
와 let
, const
를 어떻게 다르게 처리하고 있는지 뜯어보셨다. (블로그 출처는 아래에)
내용을 보니 내부적으로 var
키워드로 선언된 JS 객체와 let
, const
로 선언된 JS 객체를 분기로 갈라놓은 코드가 굉장히 많았다고! 그렇지만 자바스크립트의 호이스팅 현상은 모두 동일하게 일어나는데, V8 엔진 내부의 호이스팅 플래그인 should_hoist
값을 JavaScript 객체에 할당할 때 변수 선언 키워드에 대한 구분을 하지 않고 무조건 true
를 할당한다고 한다.
단, 변수를 위해 메모리에 공간을 확보하는 초기화 단계에서는 이 키워드들을 다르게 처리한다.
위 코드는 블로그에 있는 내용인데, V8 내부에서 사용하는 Message Template 로직의 일부이다. 선언되지 않은 변수와 초기화가 필요한 변수에 대한 에러 메시지가 다르게 설정되어 있다. 위의 "Cannot access '%' before initializtion"은 맨 처음에 우리가 콘솔에서 확인했던 에러와 같다.
위 부분을 보면 VariableMode가 var를 사용한 환경에서는 kCreatedInitialized를 반환하고, 그렇지 않으면 kNeedsInitialization을 반환한다. 정확히 어떤 값인지는 알 수 없지만 변수명을 통해 초기화 단계가 다르게 동작함을 알 수 있다. 즉 let
과 const
도 호이스팅 되어 스코프에 변수 객체로 참조되지만, 초기화가 필요한 상태로 관리되는 것이다.
var
로 변수를 선언한 경우에는 위처럼 AllocateTo 함수가 실행되면서 메모리 할당이 이루어지는 단계로 넘어가지만, let
과 const
는 set_initializer_position이라는 함수가 실행된다. C++ 파일인 것 같아서 정확하게 읽을 수는 없었지만 분기 처리된 로직을 보니 생각보다 흥미로웠다.
이렇게 let
과 const
는 호이스팅 되어 실행 컨텍스트에 변수로 등록은 되었지만 아직 초기화가 되지않아 값을 위한 공간이 메모리에 할당되지 않은 상태에 있게 되는데, 이 상태를 TDZ(Temporal Dead Zone)이라고 한다. 값이 할당되기 전에 값을 참조하려고 할 경우 TDZ에서 ReferenceError를 발생시킨다.
참고 :
JavaScript의 let과 const, 그리고 TDZ
(https://evan-moon.github.io/2019/06/18/javascript-let-const/)