console.log(a); // 출력: undefined
console.log(b()); // 출력: "Hello!"
console.log(c); // 오류: c is not defined
var a = 'hi!';
function b() {
return 'Hello!';
}
let c = 'ok!';
위 코드에서 console.log(a);의 부분은 얼핏 보았을 때 존재하지 않는 값을 가져오기 때문에 에러를 발생시켜야할 것 같다. 그런데 실제로 코드를 실행시켜보면 에러가 아닌 undefined의 값을 출력되게 된다.
자바스크립트에서는 이런 현상을 호이스팅이라고 부른다. 호이스팅에 의해 변수와 함수의 선언부는 코드 최상단으로 끌어올려 실행되게 된다.
그런데 이 현상에서 실제 코드가 물리적으로 위치를 바꾸는 것은 아니다. 자바스크립트의 내부 동작 방식에 의해서 선언부가 최상단으로 끌어올려지는 것으로 보일 뿐, 실제 코드의 위치는 변하지 않는다.
실행 컨텍스트의 내용은 이전 포스트에서 한번 다룬 바 있다. 따라서 이 글에서는 호이스팅과 실행 컨텍스트의 관계에 대해서만 정리하려고 한다.
자바스크립트의 모든 코드는 실행 컨텍스트를 가지고 있고, 코드의 동작은 반드시 실행 컨텍스트 내부에서만 이루어진다. 실행 컨텍스트는 내부에 존재하는 변수, 함수 등의 정보를 포함하고 있는데 함수 선언은 이름과 함께 전체 함수를 저장하고, 변수 선언은 이름만 저장하고 값을 undefined로 초기화하여 저장하게 된다.
console.log(a); // 출력: undefined
console.log(b()); // 출력: "Hello!"
console.log(c); // 오류: c is not defined
var a = 'hi!';
function b() {
return 'Hello!';
}
let c = 'ok!';
그리고 위에서 언급했던 코드를 다시 살펴보자. 실행 컨텍스트가 함수 정보는 모두 가지고 있기 때문에 문자열 'Hello!'가 온전히 출력된 반면에 변수 a의 경우 선언부만 가지고 있어서 undefined가 출력되었다.
호이스팅 현상은 이런 실행 컨텍스트의 특징과 자바스크립트 코드의 동작 방식에 의해 발생하는 것이다.
console.log(c); // 오류: c is not defined
let c = 'ok!';
그런데 let으로 선언된 변수 c의 경우 호이스팅 현상이 일어나지 않고, 바로 에러가 발생하였다. let도 변수의 선언 방식 중 하나인데 어째서 호이스팅 현상이 발생하지 않았을까?
var: 변수 선언이 스코프의 최상단으로 끌어올려집니다. 따라서 변수 선언 전에 변수를 참조하면 undefined를 반환합니다.
let: 변수 선언도 스코프의 최상단으로 끌어올려지는 것처럼 동작합니다. 하지만 변수를 초기화하기 전까지의 구간을 TDZ(Temporal Dead Zone)이라 하며, 이 구간에서 변수에 접근하려고 하면 오류(ReferenceError)가 발생합니다.
var: 함수 레벨 스코프(function-level scope)를 갖습니다. 즉, 변수는 가장 가까운 함수의 바깥쪽 범위에 선언됩니다. 만약 함수 외부에서 선언되었다면, 전역 스코프에 할당됩니다.
let: 블록 레벨 스코프(block-level scope)를 갖습니다. 중괄호 { } 안에 있는 어떤 블록에서든 변수가 선언될 때 그 범위 내에서만 사용됩니다.
var: 같은 스코프 내에서 동일한 이름의 변수를 재선언해도 아무런 오류가 발생하지 않습니다.
let: 같은 스코프 및 같은 블록 내에서 동일한 이름의 변수를 재선언하려고 하면 오류(SyntaxError)가 발생합니다.
var: 선언과 동시에 자동으로 undefined로 초기화됩니다.
let: 명시적으로 초기화되지 않으면 TDZ에 머물게 되며, 초기화되기 전에는 변수에 접근할 수 없습니다.
TDZ는 let과 const 변수 선언에 관한 중요한 개념이다. 변수가 선언된 위치와 초기화(값을 할당받는 것)된 위치 사이의 구간을 의미하며, 이 구간에서 해당 변수에 접근하려고 시도하면 오류가 발생하게 된다.
간단하게 설명하자면 변수가 선언된 다음, 값이 초기화 되기까지의 시간 차이 혹은 코드 상의 구간을 TDZ라고 부르는 것이다.
TDZ의 존재 이유?
이런 해괴한 개념은 왜 존재하는 것일까? TDZ는 다름아닌 var의 호이스팅 현상에서 발생하는 문제를 해결하기 위해 도입되었다. 실행 컨텍스트의 특징에 의해 호이스팅 현상이 발생하였고, 이로 인해 var로 선언한 변수는 값이 초기화 되기 전에 접근이 가능해지게 되었다. 이렇게 되자 값이 없는 변수로 인한 버그가 발생할 위험성이 생기게 된 것이다. TDZ는 외부에서 변수로 접근하기 전에 값을 할당하는 것을 강제하여 이런 문제를 방지하게 된다.
함수의 매개변수의 TDZ
ES6의 기본 매개변수도 TDZ의 개념을 따른다. 만약 기본 매개변수의 앞에 있는 매개변수를 이용하여 기본 매개변수를 초기화하려고 할 경우 TDZ 오류가 발생하게 된다.
function func(x = y, y = 1) {
console.log(x, y);
}
func(); // ReferenceError