본문 바로가기

Front-End: Web/JavaScript

<호이스팅>이 뭘까? 함수/변수 선언이 끌어올려지는 이유

반응형

 

 

 

호이스팅(hoisting)

함수의 두 가지 리터럴 형태

1. 함수 선언

함수 선언은 function 키워드 뒤로 함수의 이름을 적어서 사용합니다.

function test() {
    return "test";
}

 

2. 함수 표현식

함수 표현식은 function 키워드 뒤로 이름을 적지 않고, 변수에 함수를 집어 넣어 사용합니다.
이름이 없기 때문에 익명 함수라고 부르기도 합니다.

var testValue = function() {
    return "testValue";
}

 

 

함수 선언은 코드를 실행할 때 함수를 포함하는 스코프 최상단으로 끌어올려진다

아래 코드에서 test()와 testValue는 둘 다 잘 작동할까요?

console.log(test()); // test
console.log(testValue()); // ⚠️ error: testValue is not a function

// 함수 선언식
function test() {
    return "test";
}

// 함수 표현식
var testValue = function() {
    return "testValue";
}

정답은 함수 선언식은 잘 작동하지만,
함수 표현식은 에러가 발생합니다.
왜일까요?

왜냐하면 함수 선언은 코드를 실행할 때,
함수를 포함하는 스코프 최상단으로 끌어올려지기 때문입니다.
그래서 함수 선언 전에 함수를 실행해도 에러가 발생하지 않습니다.

즉, 아래 코드와 동일히 작동하게 되는 것이지요.

function test() {
    return "test";
}

console.log(test());

이러한 자바스크립트의 특성을 호이스팅(hoisting)이라고 합니다!

호이스팅(hoisting): 무언가를 끌어올리다

 

 

그에 반해, 함수 표현식은 변수를 통해 함수를 참조하므로 호이스팅이 일어나지 않는다

왜 표현식은 호이스팅이 일어나지 않을까요?
변수를 통해 함수를 참조한다고 했었는데, 그럼 변수가 뭔가 수상합니다.

 

변수는 이름만 호이스팅되고, '값'은 호이스팅되지 않음

아래 코드를 살펴봅시다.
undeclared는 선언되지 않은 변수이고, testValue는 선언된 변수입니다.

console.log(undeclared); // ⚠️ error: undeclared is not defined 
console.log(testValue); // undefined

var testValue = 100;

해당 결과를 살펴보았을 때, 두 가지를 알 수 있습니다.

  1. 선언하지 않은 변수는 당연히 에러가 난다.
  2. 하지만 선언한 변수도 '값'까지는 끌어올려지지는 않는다.
    선언한 변수 이름만 끌어올려진다.

 

다시 이전 코드를 살펴봅시다.

console.log(test()); // test
console.log(testValue()); // ⚠️ error: testValue is not a function

// 함수 선언식
function test() {
    return "test";
}

// 함수 표현식
var testValue = function() {
    return "testValue";
}

testValue 변수명은 호이스팅 되었으나, 값은 호이스팅되지 않았으므로
함수가 아니라고 에러가 나는 것이지요.

 

함수 표현식도 "선언 자체"는 호이스팅이 된다

console.log(testValue); // undefined

var testValue = function() {
    return "testValue";
}

이 경우, testValue라는 변수는 undefined로 먼저 호이스팅됩니다.
즉, "함수 표현식은 호이스팅이 되지 않는다"라는 표현은 정확히 말하자면, 다음과 같습니다.

변수 선언 자체는 호이스팅되지만, 
초기화(함수 대입)는 호이스팅되지 않는다

 

 

함수 표현식에서도 이름 있는 함수 표현식(Named Function Expression)

그럼 다음과 같이 함수 선언을 표현식처럼 사용한 경우는 어떨까요?

console.log(testValueVar()); // ⚠️ error: testValueVar is not a function

var testValueVar = function testValue() {
    return "hoist test";
}

똑같이 에러가 발생합니다.
변수는 함수와 달리, 선언만 끌어올려진다는 점에 주의합시다.
값은 끌어올려지지 않아요.

 

추가로 설명하자면
이 경우, testValue는 함수 내부에서만 참조 가능하므로 외부에서는 사용할 수 없습니다.

아래 예시를 살펴봅시다.

console.log(testValue());     // ❌ ReferenceError
console.log(testValueVar());  // ✅ "hi"

즉, 표현식에도 이름을 붙일 수 있습니다.

 

 

var로 선언된 변수의 호이스팅

var는 블록 스코프를 따르지 않는다 (if안의 var)

만약 이렇게 if문에서 변수의 선언과 초기화가 이뤄지는 경우는 또 어떨까요?

console.log(test); // ⚠️ undefined

var condition = false;

if(condition) {
    var test = "this is test";
}

if문이 통과가 안될텐데도 test 변수를 알고 있습니다.

if문 자체는 블록이지만, var는 블록 스코프를 따르지 않기 때문에 전역 공간에 선언된 것처럼 작동합니다.
그렇기 때문에 test 선언만 끌어올려지고, 아직 초기화는 안 되었으므로 undefinfed가 나오게 됩니다.

 

var는 함수 스코프를 따른다 (함수 안의 var)

다음과 같이 함수 안에서 변수의 선언과 초기화가 이뤄지는 경우는 어떨까요?

console.log(test()); // "hoist test"
console.log(value); // ⚠️ ReferenceError: value is not defined

function test() {
     var value = "hoist";
     return value + "test";
 }

test()는 함수 선언식이므로 당연히 위로 호이스팅되어 잘 동작됩니다.

변수는 자신의 스코프 최상단으로 끌어올려진다고 했었죠.
value 변수는 함수 스코프 안에 있습니다.

var는 "함수 스코프"를 따라요. 
자바스크립트에서 var는 블록 스코프를 무시하고, 오직 함수 스코프만 따릅니다.

var value는 test 함수 내부 스코프에만 존재합니다.
전역에서는 value라는 이름 자체가 정의되어 있지 않아요.

즉, var 변수는 선언된 함수 안에서만 호이스팅 되므로, 바깥에서는 존재조차 하지 않는 변수로 간주됩니다.

 

선언 위치 스코프 전역에서 접근 시 결과
if 블록 무시됨 (var는 함수 스코프만 가짐) undefined (선언은 되어 있음)
함수 안 함수 스코프 ❌ ReferenceError (선언 자체 없음)
if문은 블록 스코프이지만,
var는 블록 스코프를 따르지 않고 함수 스코프만 따르기 때문에 전역 공간에 선언된 것처럼 호이스팅됩니다.

반면, 함수 내부에서 선언된 var 변수는 해당 함수의 지역 스코프에만 존재하며,
함수 외부에서는 변수명 자체가 정의되지 않아 ReferenceError가 발생합니다.

 

 

var로 선언된 변수의 '중복 선언 허용'과 호이스팅의 관계

var a = 10;
var a = 20;
console.log(a); // 20

var는 중복 선언이 허용되므로,
호이스팅 시 이런 혼란이 발생할 수 있습니다.

그래서 let, const라는 새로운 선언이 나오게 된 것이지요.

 

 

let, const, class의 호이스팅

let, const, class는 왜 에러가 날까요?

console.log(test1); // ⚠️ ReferenceError: Cannot access 'test1' before initialization
console.log(test2); // ⚠️ ReferenceError: Cannot access 'test2' before initialization
console.log(Tester); // ⚠️ ReferenceError: Cannot access 'Tester' before initialization

let test1 = "let value";
const test2 = "const value";

class Tester {
    constructor() {
        this.name = "test";
    }
}

let tester = new Tester();

 

많이들 "let과 const는 호이스팅이 안 된다"고 설명하지만,
정확히 말하면 "호이스팅은 되지만, 초기화되지 않아서 사용할 수 없는 상태"입니다.

 

var와의 차이점은?

var는 선언과 동시에 undefined로 초기화되기 때문에,
선언 전에 접근해도 에러는 안 납니다.

반면 let, const, class는 선언만 호이스팅되고 초기화는 지연이 됩니다.
이 변수들은 처음에 "TDZ(Temporal Dead Zone, 일시적 사각지대)"에 들어가게 되며,
코드가 변수 선언문에 도달하여 실행되기 전까지는 접근 자체가 금지되어요.

따라서 선언 전에 접근하면 ReferenceError가 발생하는 것이지요.

 

class도 마찬가지

class도 let, const와 동일하게 TDZ에 존재하기 때문에
선언 전에 인스턴스를 만들 수가 없습니다.

const obj = new Example(); // ReferenceError
class Example {}

 

 

 

✨ Recap

  • 함수 선언과 변수 선언은 코드를 실행할 때 해당 선언 스코프 최상단으로 끌어올려집니다.
    이런 현상을 "호이스팅"이라고 합니다.
  • 선언된 변수의 값은 끌어올려지지 않습니다.
  • let, const, class 선언은 호이스팅은 되지만, 초기화 되기 전까지 TDZ에 머무르기 때문에
    선언 전에 접근하면 ReferenceError가 발생합니다.

 

반응형