자바스크립트에서 변수가 선언되기 전에 접근하면 어떤 결과가 발생할까?
룰과 매너를 지킨 개발자라면 다음과 같은 에러가 발생할 것이다.
console.log(a);
const a = "asd"
Uncaught ReferenceError: a is not defined ...
a라는 변수가 선언되기 전에 호출을 했으니 당연한 결과일 것이다.
하지만 var 키워드를 사용한다면 다음과 같은 결과가 발생할 것이다.
console.log(a);
var a = "asd"
undefined
undefined 라고 출력이 되고 에러를 발생시키지 않는다.
지난번 스코프에서도 경험했지만 var 키워드를 멀리해야 하는 이유 중 하나이다.
JavaScript로 로직을 작성하는 과정에서 에러를 만나게 되면 동작을 멈추고 화면에 노출된다.
그런데 var 키워드는 에러를 발생시키는 것이 아니라 undefined로 참조가 되면서 해당 변수의 선언 코드를 만나기 전까지는 아무런 데이터가 담기지 않은 채로 로직이 동작하게 된다.
개발자는 문제를 미연에 방지하기 어려워 질 것이며, 프로젝트의 크기가 커지면 문제 발생 지점을 찾기 어려워 질 것이다.
이러한 문제가 발생하는 이유는 var로 선언된 변수는 JavaScript를 해석하는 과정에서 선언 단계가 위로 끌어 올려진 듯 동작하기 때문이다. 이러한 개념을 호이스팅이라고 한다.
호이스팅
함수, 변수의 선언이 위로 끌어올려진 것처럼 동작하는 것을 호이스팅이라고 한다.
JavaScript에서 이런 동작이 발생하는 이유는 JavaScript가 코드를 해석하고 실행하는 과정과 내부적인 변수의 선언, 할당 과정 때문이다. 코드를 실행하기 전에 먼저 선언된 변수, 함수 등을 전역 환경에 담아두게 된다.
이후 정리된 코드를 실행하는 과정을 수행하게 된다.
변수 선언의 관점에서는 선언된 변수, 함수 등을 전역 환경에 담아두는 시기에 생성 단계라고 하는 과정을 거치게 된다.
변수의 생성 단계
우리가 선언하는 변수는 선언 단계, 초기화 단계, 할당 단계를 거치게 된다.
선언 단계
자바스크립트 엔진에 변수 객체를 등록하는 단계를 말한다.
var variable1;
let variable2;
const variable3;
초기화 단계
자바스크립트 엔진에 등록한 변수의 메모리 공간이 생성되고, 값이 존재하지 않으면 undefined가 할당된다.
let variable1 = "abc";
let variable2;
초기화 단계는 자바스크립트가 실제로 코드를 실행할 때 해당 변수의 선언 코드를 만나면 수행하게 된다.
할당 단계
변수에 사용자가 원하는 값을 할당 연산자를 통해 할당하는 단계이다.
let variable1;
variable1 = "abc";
let variable2 = "abc";
변수 값 할당 단계는 선언 후 값을 할당할 수 있고, 선언과 동시에도 가능하다.
앞서 예시로 본 let, const 키워드는 호이스팅이 발생하지 않는 것처럼 동작했지만 실제론 호이스팅이 발생한다.
자바스크립트가 실제로 코드를 실행하기 전에 선언 단계를 거치기 때문이다.
선언 단계를 거친다면 이미 객체에 등록이 되어있을 텐데, let과 const가 선언 코드를 만나기 전에 참조를 시도하면
에러가 발생하는 이유가 뭘까?
이유는 TDZ ( Temporal Dead Zone ) 때문이다.
let과 const는 선언 단계와 초기화 단계가 분리되서 실행되는데, 그 사이에 TDZ가 존재한다.
선언문을 미리 실행하기 때문에 발생하는 호이스팅이지만, TDZ 안에 존재하는 동안 호출하면 에러가 발생한다.
TDZ
TDZ는 선언 전에 변수를 사용하면 ReferenceError를 발생시키는 영역이다.
const & let 변수
선언 및 초기화 전까지 TDZ에 존재하게 된다.
variable; // << ReferenceError
const variable;
선언 한 후에 사용해야 한다.
class
class 역시 선언 전에는 사용할 수 없다.
const myCustom = new Custom(""); // << ReferenceError
class Custom {
constructor() {
// ...
}
}
마찬가지로 동작하려면 클래스를 선언한 후 사용해야 한다.
만약 부모 클래스를 상속받았다면, 생성자 안에 super()를 호출하기 전까지 this 바인딩은 TDZ에 있다.
class Custom extends PerentClass {
constructor(vari) {
this.variable = vari;
super(...);
}
}
const custom = new Custom("vari"); // ReferenceError
부모 클래스의 생성자를 호출해서 인스턴스 초기화로 인스턴스가 준비되면 자식 클래스에서 this 값을 변경할 수 있다.
함수 매개변수
글로벌과 함수 스코프 사이의 중간 스코프에 위치하는 기본 매개변수 또한 TDZ 제한이 있다.
const a = 2;
function square (a = a) {
return a * a;
}
square() // ReferenceError
square 함수의 a 매개 변수는 a = a로 초기화를 하고 있다. 하지만 이때 a 는 아직 선언이 되기 전이라 에러가 발생한다.
함수 밖에 있는 a 변수를 사용하려고 하지만 매개변수의 초기화로 사용되는 a는 const a가 아닌 매개변수 a이다.
이땐 const a의 변수 명칭을 다른 명칭으로 변경해서 사용한다.
const init = 2;
function square (a = init) {
return a * a;
}
square()
정리하면 선언 단계와 초기화 단계가 분리되어 실행되면 사이에 TDZ가 존재하게 된다.
초기화 단계를 거치기 전에, TDZ 공간에 존재하는 변수를 참조하려 시도하면 참조 에러 ( Reference Error )가 발생한다.
하지만 var 키워드는 다르게 동작한다. 선언 단계와 초기화 단계가 함께 이루어진다.
변수를 자바스크립트 엔진에 담아주는 과정 선언 단계와 해당 변수에 메모리 공간을 할당해주는 초기화 단계가
함께 수행되는 것이다. 즉, 선언 단계와 초기화 단계 사이에 있는 TDZ가 존재하지 않게 되는 것이다.
그렇기 때문에 JavaScript가 선언된 변수와 함수를 정리하는 과정에서 이미 임시 메모리를 할당 받았기 때문에
참조 에러가 아닌 undefined를 할당 받은 것이다.
함수도 마찬가지로 함수 표현식은 TDZ의 영향을 받기 때문에 호이스팅이 발생하지 않는 것처럼 동작하고,
함수 선언식의 경우 undefined가 할당되는 것이 아닌 함수 자체가 끌어올려진 것처럼 동작하게 된다.
// 함수 표현식
const fn1 = function () {
console.log("TDZ 영향을 받는다.");
}
// 함수 선언식
function fn2 () {
console.log("TDZ 영향을 받지 않는다.");
}
'JavaScript' 카테고리의 다른 글
얕은 복사 & 깊은 복사 (3) | 2024.09.25 |
---|---|
Object (1) | 2024.09.21 |
함수 스코프, 블록 스코프 (1) | 2024.08.14 |
JavaScript defer & async (2) | 2024.06.27 |
JavaScript 전역변수 (2) | 2024.06.22 |