어렴풋하게는 인지하고 있지만,
명확하게 설명하려면 헷갈리는 개념인 호이스팅과 TDZ, 그리고 자바스크립트에서 변수의 동작 과정에 대해 알아보자.
0. 변수의 실행 흐름
호이스팅 개념을 정확히 이해하려면, 먼저 자바스크립트에서 변수 선언이 어떻게 처리되는지 이해할 필요가 있다.
자바스크립트에서 변수는 다음과 같은 단계를 거친다.
선언 → 초기화 → 할당
일반적으로 개발자는 이 과정을 단순히 "변수를 선언하고 값을 넣는 것"으로 인식하지만, 실제 자바스크립트 엔진은 이 과정을 보다 정교하게 처리한다. 특히, 실행 컨텍스트 생성 단계에서 변수에 어떤 작업이 이뤄지는지를 이해해야 호이스팅과 TDZ를 정확히 설명할 수 있다.
Q. 실행 컨텍스트가 뭔가요?A. 코드가 실행되는 환경(스코프)에 대한 정보를 담고 있는 실행 단위입니다.
Q. 환경 레코드가 뭔가요?A. 자바스크립트 엔진이 현재 스코프에서 선언된 변수들을 저장해둔 내부 저장소입니다.
이제 이 개념을 바탕으로, 호이스팅이 어떤 동작인지 살펴보자.
1. 호이스팅이란?
호이스팅을 아주 간단히 설명하자면, 변수, 함수, 클래스, 임포트 선언을 가장 위로 끌어올리는 것을 의미한다. (실제로 끌어올리는 것은 아니고, 그렇게 보이는 거다.)
자바스크립트 코드를 실행하게 되면
여기서 먼저 스캔해 환경 레코드에 등록하는 과정을 바로 호이스팅이라고 할 수 있다.
예시와 함께 살펴보자.
console.log(a); // undefined
var a = 10;위 코드에서는 변수를 선언하기 전에 변수를 호출하고 있으니 ReferenceError(참조 에러)가 발생해야 할 것 같지만, 실제로 실행해보면 undefined가 출력되는 것을 확인할 수 있다.
다른 예시도 확인해보자.
console.log(hello); // [Function: hello] -> 실행환경에 따라 다를 수 있음 참조 에러가 나지 않는 점에 주목
console.log(hello()); // Hello!
function hello () {
return 'Hello!';
}function hello는 선언 이전에 호출되지만, 아까와 마찬가지로 ReferenceError가 발생하지 않고 함수가 제대로 발동되는 것을 확인할 수 있다.
여기서 변수와 함수 선언식의 호이스팅 차이가 보이는데, 변수의 경우 선언부만 호이스팅되며 변수 할당 부분은 호이스팅되지 않아 undefined가 출력되지만, 함수선언식의 경우 선언과 구현부가 함께 호이스팅되어 온전한 함수 동작 전체가 실행되는 것을 확인할 수 있다.
MDN 문서에서는 이를 각각 선언 호이스팅과 값 호이스팅이라고 명명하고 있다.
(하지만 ECMAScript 사양에는 애초에 호이스팅이라는 용어가 등장하고 있지 않기에, 용어를 외울 필요는 없고 변수와 함수는 차이가 있구나 정도만 이해하고 넘어가도 됩니다.)
2. var, let, const에서의 호이스팅
위에서 호이스팅이란 변수, 함수 등을 미리 스캔해 환경 레코드에 등록하는 행동이라고 설명했었다.
그렇다면 var 외에 let과 const에서는 어떤 결과가 일어날까?
console.log(a); // undefined
var a = 10;
console.log(b); // ReferenceError
let b = 10;
console.log(c); // ReferenceError
const c = 10;위에 적힌 코드를 두줄씩 실행해보면, var의 경우 undefined가 뜨긴 하지만 에러는 발생하지 않고, let과 const의 경우 ReferenceError가 발생하며 프로그램이 종료되는 것을 확인할 수 있다.
이러한 차이가 왜 일어나는 걸까?
let과 const는 호이스팅이 발생하지 않는걸까?
결론부터 말하자면 둘 다 어느정도 맞는 말이라고 할 수 있다.
let과 const도 var와 마찬가지로 프로그램 실행 시 컴파일러가 변수를 우선적으로 환경 레코드에 추가하는 과정을 거친다.
하지만, 변수 스코프의 맨 위에서부터 변수의 초기화 완료시점 전까지는 해당 변수가 Temporal Dead Zone (TDZ), 즉 일시적 사각지대에 놓이게 된다.
여기서 주목할 부분이 두가지 있는데, 첫 번째는 변수의 초기화 완료 시점의 정의고, 두번째는 왜 "일시적" 사각지대인지이다.
변수의 초기화 완료 시점이란 위에서 정의한대로 엔진이 내부적으로 변수에 접근 가능하게 준비되는 시점을 의미한다. 다만 let과 const가 var와 다른 점은, var의 경우 선언과 동시에 호이스팅으로 끌어올려지는 순간에 바로 undefined라는 값으로 초기화가 완료되지만, let과 const는 실제 코드의 선언, 할당부에 도달하고서야 초기화가 완료된다는 것이다.
var x = 10;
// 변수 스코프 진입시 x가 환경 레코드에 등록되면서 undefined로 초기화 되고,
// 해당 코드가 실행 시점에 도달한 순간 x에 10이 할당된다.
let y;
// 변수 스코프 진입시 y가 환경 레코드에 등록되지만 초기화가 되지 않은 TDZ에 놓이게 되고,
// 해당 코드가 실행 시점에 도달한 순간 y가 undefined로 초기화된다.
const z = 10;
// 변수 스코프 진입시 z가 환경 레코드에 등록되지만 초기화가 되지 않은 TDZ에 놓이게 되고,
// 해당 코드가 실행 시점에 도달한 순간 z가 10으로 초기화된다.
// const의 경우 선언과 동시에 값이 할당되어야 한다.그렇다면, TDZ, 즉 일시적 사각지대란 무엇일까?
TDZ란 스코프의 시작부터 해당 변수의 선언문이 실행되기 전까지의 시점을 의미하며, 변수에 접근이 불가능한 구간이다.
일시적이라는 뜻이 붙은 이유는 뭘까?
MDN 문서에 따르면, 사각지대가 **코드의 작성 순서(위치)**에 따라 형성되는게 아니라 **코드의 실행 순서(시간)**에 따라 실행되기 때문이라 설명되어 있다.
MDN에 나와있는 아래 예시와 함께 확인해보면 쉽게 이해할 수 있다.
{
// TDZ가 스코프 맨 위에서부터 시작
const func = () => console.log(letVar); // OK
// TDZ 안에서 letVar에 접근하면 ReferenceError
let letVar = 3; // letVar의 TDZ 종료
func(); // TDZ 밖에서 호출함
}여기까지 정리한 후에, 위의 질문에 다시 답변해보자.
let과 const는 호이스팅이 발생하지 않는걸까?
위에서 말했듯 호이스팅은 공식적인 단어가 아니기에 정의가 조금씩 다를 수 있다.
만약 이 글에서 정리한대로 호이스팅을 컴파일러가 뒤에 나오는 변수, 함수 등을 미리 스캔해 환경 레코드에 등록하는 행동이라 정의한다면, let과 const 또한 호이스팅이 발생한다고 볼 수 있다. 하지만 호이스팅이란 단어의 범위를 좁혀 변수 선언 시점 이전에 접근할 수 있게 하는 것으로 정의한다면 let과 const에서는 호이스팅이 발생하지 않는다고 볼 수 있다.
이를 거칠게 정리하면 다음과 같다.
변수는 모두 변수 스코프에 진입한 순간 환경 레코드에 수집된다.하지만 var는 선언 시점 전 호출이 가능하고, let과 const는 선언 시점 전 호출이 불가능하다.
호이스팅과 TDZ에 대한 내용은 자바스크립트의 코어 시스템과 연관이 깊어 처음 접할시 헷갈릴 수 있다.
최대한 호이스팅에 대한 내용만 담아서 이해하기 쉽게 쓰려고 노력했는데 자신은 없다...
다음 글에서는 자바스크립트의 환경 레코드, 실행 컨텍스트에 대해 더 자세히 알아보겠다.
PS. let과 const도 환경 레코드에 수집되는게 맞는지 의심이 된다면 아래 내용을 확인해보자.
더보기
const x = 1;
{
console.log(x); // 참조 에러
const x = 2;
}만약 let과 const가 환경 레코드에 수집조차 되지 않는다면 위 코드 실행 시 참조 에러가 아닌 1이 출력되어야 한다.
자바스크립트는 실행 스코프에서 맞는 변수를 찾지 못한다면 본인을 감싸는 실행 스코프로 탐색을 이어가는 특성이 있기 때문이다. (이에 대한 자세한 얘기는 지면상 생략...)