var name = "Mike";
console.log(name); // Mike
var name = "Jane";
console.log(name); // Jane
보통의 일반적인 언어라면 같은 이름을 가진 변수를 재선언하는 것은 불가능하다.
하지만 var
는 이조차도 가능하다. 현재 코드에서는 name
이 두번 선언된 것을 한 눈에 알 수 있지만 첫 번째 선언부와 두 번째 선언부 사이에 방대한 코드가 있다면 어떻게 될까?
이런 변수의 중복 선언 허용은 의도하지 않은 변수의 변경이 일어날 가능성이 높다.
var name = "Mike";
console.log(name); // Mike
name = "Jane";
console.log(name); // Jane
아래 예시를 보면 선언되기 이전에 콘솔을 출력하지만 error가 나지않고, undefined를 출력한다.
console.log(name); // undefined
var name = "Mike"; //
이는 위 코드가 아래와 같은 방식으로 동작하기 때문이다.
var name; // 호이스팅
console.log(name); // undefined
name = "Mike";
let name = "sujin";
let name = "suji"; // error 재선언 불가
name = "suji"; // 재할당은 가능
같은 변수를 같은 함수나 블록 스코프 안에서 다시 선언하려고 시도하면 SyntaxError
가 발생.
// 상수의 의미를 지닌 const는 말 그대로 고정된 값(변하고 싶지 않은 값)이다
// 때문에 재할당 재선언이 불가능하여 그 자리에서 값을 할당해주어야한다.
const COLOR_NAME = "color";
const COLOR_NAME = "blue"; // error
COLOR_NAME = "red" // error
- 바로 Temporal Dead zone (TDZ) 일시적인 사각지대 때문이다!
TDZ(Temporal Dead Zone)란,
스코프의 시작 지점부터 초기화 시작 지점까지의 구간을 말한다.
"시간상" 사각지대인 이유는, 사각지대가 코드의 작성 순서(위치)가 아니라 코드의 실행 순서(시간)에 의해 형성되기 때문.
let
과 const
는 tdz로 인해 할당을 하기 전에는 사용할 수가 없는 것.
이는 코드를 예측가능하게 하고 잠재적인 버그를 줄일 수 있게 해줌.
호이스팅은 스코프 단위로 일어난다. 때문에 아래와 같은 예제에서는 에러가 난다.
// ❌
let age = 30;
function showAge() {
console.log(age);
let age = 20; // 현재 줄의 let이 호이스팅이 일어난다.
}
showAge();
호이스팅은 스코프 단위로 일어난다.
위 예제에서 스코프는 showAge
함수 내부이기 때문에 age
를 재선언하는 구문에서 호이스팅이 일어나 에러가 발생하게 된다. 더 자세한 부분은 아래에 적어두었다.
1. 선언 단계(Declaration phase) : 변수를 실행 컨텍스트의 변수 객체에 등록하는 단계를 의미.
이 변수 객체는 스코프가 참조하는 대상이 됩니다.
2. 초기화 단계(Initialization phase) : 실행 컨텍스트에 존재 하는 변수 객체에 선언 단계의 변수를 위한 메모리를 만드는 단계.
이 단계에서 할당된 메모리에는 undefined로 초기화 됩니다.
3. 할당 단계(Assignment phase) : 사용자가 undefined로 초기화된 메모리의 다른 값을 할당하는 단계.
- 선언 및 초기화 단계
- 할당 단계
초기화: undefined
를 할당 해주는 단계
🌈 선언과 초기화 단계가 동시에 일어남
때문에 할당 전에 호출하게 되면 에러를 내지 않고 undefined
가 출력되는 것.
- 선언 단계
- 초기화 단계
- 할당 단계
var
와 달리 선언 단계와 초기화 단계가 분리되어 진행.
시간상 사각지대 (TDZ)
let 변수
는 초기화하기 전에는 읽거나 쓸 수 없음 (선언 구문에 초기 값을 지정하지 않은 경우 undefined로 초기화함). 초기화 전에 접근을 시도하면ReferenceError
가 발생.
호이스팅 되면서 선언 단계가 이루어지지만, 초기화 단계는 실제 코드에 도달했을 때 되기 때문에 var
와 달리 할당 전에 호출하게 되면 레퍼런스 에러가 발생하는 것.
console.log(a); // ReferenceError: Cannot access "a" before initialization
let a = 1;
console.log(a);
- 선언 + 초기화 + 할당
const
는 말 그대로 상수 즉, 변하지 않는 값을 일컫으므로 선언과 동시에 초기화와 할당이 일어난다. const
키워드를 사용하여 선언한 값은 두번 다시 변경할 수 없다는 뜻이다.
"TDZ 구간에 있는 변수 객체"는
선언은 되어있지만 아직 초기화가 되지않아 변수에 담길 값을 위한 공간이 메모리에 할당되지 않은 상태이다.
이때 해당 변수에 접근을 시도하면Cannot access '%' before initialization
에러 메세지를 만나게된다.
즉,변수 스코프의 맨 위에서 변수의 초기화 완료 시점까지의 변수
는 "시간상 사각지대"(Temporal Dead Zone, TDZ)에 들어간 변수라고 표현합니다.
var a = 2;
function foo() {
var boo = "boo";
}
console.log(a) // 2
console.log(boo) // error, boo is not defined
위에서 말했듯이 var
는 함수 스코프를 가지기 때문에 함수 내 변수는 지역변수가 되어 전역에서 접근이 불가능하다. 하지만 아래와 같은 예시에서는 어떻게 될까?
for (var i =0; i < 5; i++) {
console.log(i); // 0 1 2 3 4 출력
}
console.log(i); // 0 1 2 3 4 5
위 예시는 원래는 말이 되지 않는 문법이다. for
문 블록 스코프 내에서 선언한 변수이기 때문에 출력이 되지 않는 것이 맞지만 출력이 된다. var
는 함수 스코프를 가지기 때문이다. 이 외의 블록 스코프에서는 모두 전역 변수처럼 사용을 할 수가 있는 것.
함수, if문 for문, while문, try/catch 문 등
다시 한번, 위에서 보았던 예제를 변수 생성과정과 tdz 개념에 빗대어 요약해보자면
// ❌
let age = 30;
function showAge() {
console.log(age);
let age = 20; // 현재 줄의 let이 호이스팅이 일어난다.
}
showAge();
let
으로 선언된 변수는 var
키워드와는 다르게 선언단계와 초기화 단계가 분리되어서 진행이 된다. 그렇기 때문에 실행 컨텍스트에 변수를 등록(선언)했지만, 메모리가 할당이 되질 않아(초기화) 접근할 수 없어 참조 에러(ReferenceError)가 발생하는 것 이고,
때문에 우리는 이것을 보고 let
과 const
가 호이스팅이 되질 않는다!! 라고 오해할 수 밖에 없었던 것!
let과 const키워드는 var키워드처럼 호이스팅(hoisting)이 일어난다!
다만,변수 생성과정에 차이
와tdz
에 의해 우리에 눈으로는 호이스팅이 일어나지 않는 것처럼 보일 뿐이다.
MDN) let과 const로 선언한 변수도 호이스팅 대상이지만, var와 달리 호이스팅 시 undefined로 변수를 초기화하지는 않는다. 따라서 변수의 초기화를 수행하기 전에 읽는 코드가 먼저 나타나면 에러가 발생.
참고 자료) 코딩앙마 / 누구나 블로그
https://evan-moon.github.io/2019/06/18/javascript-let-const/