var
키워드의 경우 let
이나 const
와 달리 변수를 선언하기 이전에도 참조가 가능해지는 문제가 발생한다.
이 문제를 이해하기 위해서 호이스팅이라는 개념을 알고 있어야 한다.
var
의 경우 선언 이전에 참조가 가능하기에 호이스팅이 일어나지만, let
과 const
는 호이스팅이 일어나지 않는다고 착각 할 수도 있다.
하지만 let
, const
을 포함하여 모든 선언은 호이스팅이 일어난다.
> Hoisting?
자바스크립트 엔진 동작을 이해하기 쉽게 하기 위해 만들어진 개념으로
🚀 변수명 과 함수선언이 유효 스코프{}
최상단으로 끌어올려진 후 코드를 실행하는 것처럼 생각하는 것이다.
🚀 변수는 선언부와 할당부를 나누어 선언부만 끌어올리는 반면
함수 선언은 함수 전체를 끌어 올립니다. (발췌: 코어 자바스크립트p.46)
변수 선언, 함수 선언문 => 호이스팅 O
함수 표현식 => 호이스팅 X
(식별자가 호이스팅될 뿐, 함수 자체는 끌어올려지지 않는다.)
foo(); //(1)
console.log(foo1); //(2)
foo1(); //(3)
function foo() { console.log('함수 선언문'); }
var foo1 = function() { console.log('함수 표현식'); }
//Hoisting-----
var foo1; // 변수 선언이 함수 선언보다 높은 우선순위!
function foo() { console.log('함수 선언문') }
foo();
console.log(foo1);
foo1();
foo1 = function() { console.log('함수 표현식') }
➡️ (1) '함수 선언문' (2) undefined (3) TypeError: foo1 is not a function
실제로 foo1을 출력했을 때는 undefined 를 확인할 수 있으며,
함수로서 foo1() 와 같이 호출했을 때는 TypeError: foo1 is not a function 라는 에러가 발생한다.
> 호이스팅 대상
자바스크립트에서는 ES6에서 도입된 let, const를 포함한 모든 선언을 호이스팅한다.
✅ 변수의 생성 3단계
선언 단계(Declaration): 변수를 변수 객체(Variable Object)에 등록합니다. 이 변수 객체는 스코프가 참조하는 대상이 됩니다.
초기화 단계(Initialization): 변수 객체(Variable Object)에 등록된 변수를 위한 공간을 메모리에 확보합니다. 이 단계에서 변수는 undefined로 초기화됩니다.
(초기화 단계는 변수 선언문에 도달했을 때 이루어진다.)
할당 단계(Assignment): undefined로 초기화된 변수에 실제 값을 할당합니다.
📌 var 로 선언된 변수는 선언 단계와 초기화 단계가 한번에 이루어진다.
👉 초기화 단계에서 undefined로 초기화되기 때문에 변수 선언문 이전에 변수에 접근해도 에러가 발생하지 않고 undefined를 반환한다.
📌 반면 let, const 로 선언된 변수는 선언 단계와 초기화 단계가 분리되어 이루어진다.
👉 따라서, 스코프에 변수를 등록(선언단계)하지만
초기화 단계는 변수 선언문에 도달했을 때 이루어지므로
초기화 이전에 변수에 접근하면 참조 에러(ReferenceError)가 발생한다.
console.log(a); //(1)
var a = 10;
//Hoisting------
var a;
console.log(a);
a = 10;
➡️ (1) undefined
console.log(name); //(1)
console.log(last_Name); //(2)
var name = 'wooyoung';
let last_Name = 'kim';
//Hoisting-------
var name;
let last_Name;
console.log(name);
console.log(last_Name);
➡️ (1) '' (2) Uncaught ReferenceError: last_Name is not defined
let
과 const
로 선언한 변수는 var
처럼 호이스팅이 일어나지만,
var
와 다르게 초기화가 되기전까지 TDZ라는 곳에 머물러 초기화(혹은 할당)이 될 때까지 잠시 '죽어있는 상태'이기 때문에 선언 전에 참조가 불가능한 것이다.
TDZ를 직역해도 '임시로 죽어 있는 공간'이다. TDZ는 선언 전에 변수를 사용하는 것을 비허용하는 개념상의 공간이다.
var
과 달리 선언 전에 변수를 사용하지 못하는 것인데, 호이스팅이 일어난 것을 직접 확인할 수 있을까? 아래 두 예시를 보자.
1)
typeof notDefined; // 'undefined'
typeof variable; // Uncaught ReferenceError: variable is not defined
let variable;
즉, 아예 선언이 되지 않은 변수는 'undefined' 이지만,
let
은 TDZ에 있는 식별자에는 접근하는 것은 그 자체로 에러를 발생시키고 있다.
2)
let a = 3; // 전역 변수
{
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 10;
}
만약 let
이 호이스팅이 되지 않은 것이라면 2)예시와 같은 에러는 발생하지 않았을 것이다. 중괄호 스코프 내에서 호이스팅이 일어났고 a
가 스코프 내에서 할당이 일어나기 전에 console.log
에서 참조를 하려 했고, 아직 TDZ에 있는 변수 a
를 참조할 수 없기에 ReferenceError
가 발생하는 것이다.
📌 정리하면, let, const도 호이스팅이 이루어진다.
그러나 선언과 초기화가 나누어져있어서 TDZ으로 인해 에러가 발생한다!
추가 예시1)
callAge(1990); // 함수 선언 전에 함수를 호출 -> hoisting 이 되어 작동한다.
function callAge(year){
console.log(2021 - year);
} // 함수 선언문
➡️ 31
추가 예시2)
retirement(1990); // 작동하지 않는다
var retirement = function(year) {
console.log(65- (2020- year));
} // 함수의 선언 방식이 아니기 때문에 hoisting 되지 않는다.
//(only work on function declaration)
➡️ TypeError: retirement is not a function
> 호이스팅 순위
함수선언이 수식상 위에 작성되어도
👉 변수 선언이 함수 선언보다 높은 우선순위를 가진다.
function myName() {
console.log("wooyoung");
}
function yourName() {
console.log("dongyoung");
}
var myName = "robinson";
var yourName = "teddy";
console.log(myName); // (1)
console.log(yourName); // (2)
//hoisting
var myName; //변수 선언이 높은 우선순위
var yourName; //변수 선언이 높은 우선순위
function myName() {
console.log("wooyoung");
}
function yourName() {
console.log("dongyoung");
}
myName = "robinson";
yourName = "teddy";
➡️ 변수선언 -> 함수선언 -> 마지막으로 값이 robinson,teddy로 할당된다.
(1) robinson (2) teddy
var age = 50; //global
console.log(age); //(1)
function foo(){
var age = 65; //local
console.log(age); //(2) local print
}
foo();
console.log(age); //(3) global print
//hoisting
var age;
function foo(){
var age;
age= 65;
console.log(age); //(2) local print
}
age = 50;
console.log(age); //(1)
foo();
console.log(age); //(3) global print
➡️ (1) 50 (2) 65 (3) 50