함수 안에 있는 선언들을 모두 끌어 올려서 해당 함수 유효 스코프의 최상단에 선언 하는 것을 말합니다.
호이스팅의 대상
자바스크립트는 ES6에서 도입된 let, const를 포함하여 모든 선언(var, let, const, function, function*, class)을 호이스팅합니다. 호이스팅(Hoisting)이란, var 선언문이나 function 선언문 등을 해당 스코프의 선두로 옮긴 것처럼 동작하는 특성을 말합니다.
let/const 변수 선언과 함수 표현식 에서는 호이스팅이 발생하지 않습니다.
foo();
foo2();
function foo() { // 함수선언문
console.log("hello");
}
var foo2 = function() { // 함수표현식
console.log("hello2");
}
함수 선언문에서의 호이스팅
function printName(firstname) { // 함수선언문
var result = inner(); // "선언 및 할당"
console.log(typeof inner); // > "function"
console.log("name is " + result); // > "name is inner value"
function inner() { // 함수선언문
return "inner value";
}
}
printName(); // 함수 호출
함수 표현식에서의 호이스팅
/* 오류 */
function printName(firstname) { // 함수선언문
console.log(inner); // > "undefined": 선언은 되어 있지만 값이 할당되어있지 않은 경우
var result = inner(); // ERROR!!
console.log("name is " + result);
var inner = function() { // 함수표현식
return "inner value";
}
}
printName(); // > TypeError: inner is not a function
호이스팅은 함수 선언문과 함수 표현식에서 서로 다르게 동작하기 때문에 주의해야 합니다. 변수에 할당된 함수 표현식은 끌어 올려지지 않기 때문에 이때는 변수의 스코프 규칙을 그대로 따릅니다.
Q. printName에서 “inner is not defined” 이라고 오류가 나오지 않고, “inner is not a function”이라는 TypeError가 나오는 이유?
A. printName이 실행되는 순간 (Hoisting에 의해) inner는 ‘undefined’으로 지정되기 때문
inner가 undefined라는 것은 즉, 아직은 함수로 인식이 되지 않고 있다는 것을 의미합니다.
함수 표현식의 선언이 호출보다 아래에 있는 경우
/* 오류 */
function printName(firstname) { // 함수선언문
console.log(inner); // ERROR!!
let result = inner();
console.log("name is " + result);
let inner = function() { // 함수표현식
return "inner value";
}
}
printName(); // > ReferenceError: inner is not defined
console.log(inner);에서 inner에 대한 선언이 되어있지 않기 때문에 inner is not defined 오류가 발생합니다.
호이스팅 우선순위
같은 이름의 var 변수 선언과 함수 선언에서의 호이스팅
var myName = "hi";
function myName() {
console.log("yuddomack");
}
function yourName() {
console.log("everyone");
}
var yourName = "bye";
console.log(typeof myName);
console.log(typeof yourName);
/** --- JS Parser 내부의 호이스팅(Hoisting)의 결과 --- */
// 1. [Hoisting] 변수값 선언
var myName;
var yourName;
// 2. [Hoisting] 함수선언문
function myName() {
console.log("yuddomack");
}
function yourName() {
console.log("everyone");
}
// 3. 변수값 할당
myName = "hi";
yourName = "bye";
console.log(typeof myName); // > "string"
console.log(typeof yourName); // > "string"
💡 값이 할당되어 있지 않은 변수와 값이 할당되어 있는 변수에서의 호이스팅
var myName = "Heee"; // 값 할당
var yourName; // 값 할당 X
function myName() { // 같은 이름의 함수 선언
console.log("myName Function");
}
function yourName() { // 같은 이름의 함수 선언
console.log("yourName Function");
}
console.log(typeof myName); // > "string"
console.log(typeof yourName); // > "function"
Temporal Dead Zone(TDZ)이란?
선언 전에 변수를 사용하는 것을 비 허용하는 개념상의 공간입니다.
const white = '#FFFFFF';
white; // => '#FFFFFF'
이번에는 선언 전에 white 변수에 접근해봅시다.
white; // throws `ReferenceError`
const white = '#FFFFFF';
white; // => '#FFFFFF'
const white ='#FFFFFF' 구문 전 줄 까지, white 변수는 TDZ에 있습니다.
TDZ에 있는 white 변수에 접근하게 되면, ReferenceError: Cannot access 'white' before initialization 자바스크립트 에러가 발생합니다.
const 변수는 선언 및 초기화 전 줄까지 TDZ에 있습니다.
// Does not work!
pi; // throws `ReferenceError`
const pi = 3.14;
const 변수는 선언한 후에 사용해야 합니다.
const pi = 3.14;
// Works!
pi; // => 3.14
✅ let 변수
let도 선언 전 줄 까지 TDZ의 영향을 받습니다.
// Does not work!
count; // throws `ReferenceError`
let count;
count = 10;
✅ class 구문
머리맡 부분에서 보았듯이, 선언 전에는 class를 사용할 수 없습니다.
// Does not work!
const myNissan = new Car('red'); // throws `ReferenceError`
class Car {
constructor(color) {
this.color = color;
}
}
위의 예제가 동작하려면, 클래스를 선언한 후에 사용하도록 수정해야합니다.
class Car {
constructor(color) {
this.color = color;
}
}
// Works!
const myNissan = new Car('red');
myNissan.color; // => 'red'
constructor() 안에서 super()가 호출되기 전까지 this를 사용할 수 없습니다.
TDZ는 인스턴스를 초기화하기 위해 부모 클래스의 생성자를 호출할 것을 제안합니다. 부모 클래스의 생성자를 호출하고 인스턴스가 준비되면 자식 클래스에서 this 값을 변경할 수 있습니다.
class MuscleCar extends Car {
constructor(color, power) {
super(color);
this.power = power;
}
}
// Works!
const myCar = new MuscleCar('blue', '300HP');
myCar.power; // => '300HP'
기본 매개변수는 글로벌과 함수 스코프 사이의 중간 스코프에 위치한다. 기본 매개변수 또한 TDZ 제한이 있다.
const a = 2;
function square(a = a) {
return a * a;
}
// Does not work!
square(); // throws `ReferenceError`
기본 매개변수 a는 선언 전에 a = a 표현식의 오른쪽에서 사용되었다. a에서 참조 에러가 발생한다.
기본 매개변수는 선언 및 초기화 다음에 사용되어야 한다. 이 경우 init과 같은 다른 변수로 선언하여 사용한다.
const init = 2;
function square(a = init) {
return a * a;
}
// Works!
square(); // => 4
반대로 var, function 선언은 TDZ에 영향을 받지 않는다. 이것들은 현재 스코프에서 호이스팅이 됩니다.
var 변수는 선언하기 전에 접근하면, Undefined를 얻게 됩니다.
// Works, but don't do this!
value; // => undefined
var value;
그러나 함수는 선언된 위치와 상관없이 동일하게 호출됩니다.
// Works!
greet('World'); // => 'Hello, World!'
function greet(who) {
return `Hello, ${who}!`;
}
// Works!
greet('Earth'); // => 'Hello, Earth!'
함수선언 전에 호출해도 에러가 발생하지 않는 이유는 호이스팅 때문입니다.
흥미로운 점으로 import 모듈 또한 호이스팅이 됩니다.
import { myFunction } from './myModule';
import 구문이 호이스팅이 되기 때문에, 자바스크립트 파일 시작 부분에서 디펜던시 모듈을 가져오는 것이 좋습니다
typeof 연산자는 변수가 현재 스코프 안에 선언되었는지 확인하는 경우 유용합니다.
예를 들어서, notDefined 변수는 선언되지 않았습니다. 이 변수에 typeof 연산자를 적용하면 에러가 발생합니다.
typeof notDefined; // => 'undefined'
변수가 선언되지 않았기 때문에, typeof notDefined는 undefined로 평가합니다.
그러나 TDZ의 변수에서 typeof 연산자를 사용하면 다르게 동작합니다. 다음과 같은 경우에 에러가 발생합니다.
typeof variable; // throws `ReferenceError`
let variable;
TDZ는 선언문이 존재하는 스코프 범위 안에서 변수에 영향을 줍니다.
호이스팅은 함수 안에 있는 선언들을 모두 끌어 올려 해당 함수 유효 범위의 최상단에 선언하는 것을 말합니다.
TDZ는 const, let, class는 TDZ에 영향을 받습니다. 즉 const, let, class는 선언 전에 변수를 사용하는것을 허용하지 않습니다.
반대로 var, function, import의 선언은 TDZ의 영향을 받지 않습니다. 특히 var 변수는 선언 전에도 사용할 수 있는 점에서 var 변수 사용을 피해 예기치 못한 오류를 방지하는 것이 좋습니다.
변수가 먼저 선언이 된 경우, 초기화에 따라서 TDZ가 생깁니다. 특히 let,const와 var는 초기화 시점이 다릅니다. var는 암묵적으로 undefined로 초기화 된 상태에서 자바스크립트 코드를 읽기 때문에, TDZ에서 에러가 나지 않습니다.
TDZ를 통해 스코프와, 호이스팅을 살펴보고 어떤식으로 TDZ와 호이스팅이 연관되어 있는지 알아 볼 수 있는 좋은 개념이였습니다.