[JS] 호이스팅

강풍윤·2023년 3월 11일
0
post-thumbnail

1. 호이스팅이란?

호이스팅이란 선언문이 마치 최상단에 끌어올린 듯한 현상을 의미합니다.

2. 자바스크립트 기본 동작방식

자바스크립트 엔진의 동작은 간단하게 생성단계와 실행단계로 구분됩니다.
먼저 생성단계에서는 값을 할당하지 않고, 선언문만 실행해서 기록합니다.
그 이후에 실행단계에서 선언문을 제외한 나머지 코드를 런타임 실행처럼 순차적으로 실행합니다.

다음 코드를 통해 자바스크립트의 동작방식을 알아보기로 합시다.

위의 코드는 먼저 콘솔로 varHoisting 값을 확인하고, "This is var Hoisting"이란 값을 할당한 varHoisting라는 변수를 선언하고, 다시 콘솔로 varHoisting 값을 확인하는 간단한 코드입니다.

자바스크립트는 엔진의 동작은 간단하게 생성단계와 실행단계로 구분하는데, 가장 먼저 생성단계에서 전체 코드에서 선언문만 따로 실행하여 값을 환경 레코드(Enviromnet Record)에 기록합니다. 여기서 환경 레코드는 다음 포스트에서 자세히 다룰 예정이라, 이번 포스트에선 환경 레코드를 값을 기록하는 공간이라고 간단하게 설명하겠습니다. 여기서 선언문만 실행한다면, varHoisting라는 변수만 환경레코드에 기록되고 값은 저장되지 않는게 정상적인 프로세스인데, var는 자바스크립트의 다른 변수 키워드들과 다르게 선언과 동시에 값을 undefined로 초기화 합니다.

실행단계에서는 이전 생성단계에서 환경 레코드에 기록된 값을 가지고서 전체 코드를 순차적으로 실행합니다. 첫 번째 라인은 콘솔로 varHoisting 값을 확인합니다. 이때 varHoisting 값은 환경 레코드에 기록된 값인 undefined가 됩니다.

두 번째 라인에서는 varHoisting 이란 변수의 값을 "This is var Hoisting"이란 값으로 할당하기 때문에, 기록된 환경 레코드의 값도 수정되게 됩니다.

마지막으로 세 번째 라인에서는 이전 라인에서 할당된 환경 레코드의 값을 가지고서 콘솔로 varHoisting 값을 확인합니다. 이때 varHoisting의 값은 두 번째 라인에서 할당된 값으로 실행됩니다.

3. 호이스팅 종류

(1) 변수 호이스팅
1-1) var
1-2) let, const

(2) 함수 호이스팅
2-1) 함수표현식
2-2) 함수선언식

(3) 클래스 호이스팅

(1) 변수 호이스팅

1-1) var 호이스팅

console.log(varHoisting); // undefined

var varHoisting = "This is var Hoisting";

console.log(varHoisting); // This is var Hoisting

위의 코드를 실행하면 앞서 설명한 내용처럼 자바스크립트 동작 방식에 의해 먼저 선언된 변수의 값이 선언과 동시에 undefined값을 가진 채로 환경 레코드에 기록됩니다. 이후 환경 레코드에 기록된 값을 가지고 순차적으로 실행하여 진행하게 됩니다. 따라서 첫 번째 코드 라인은 undefined를 호출하고, 세 번째 코드 라인의 콘솔에는 두 번째 코드 라인에서 할당된 값인 "This is var Hoisting"이 호출됩니다.

1-2) let, const 호이스팅

console.log(constHoisting); // Reference Error

const constHoisting = "This is const Hoisting";

console.log(constHoisting);

let과 const는 var 키워드와 마찬가지로 생성단계에서 선언문을 실행하여 환경 레코드에 기록하지만, var 키워드에선 선언과 동시에 값을 undefined로 초기화하는 반면에, let과 const는 값을 초기화하지 않습니다. 따라서 환경 레코드에는 varHoisting라는 변수명은 기록되지만 그 변수의 값은 존재하지 않고 참조하지 않기 때문에 실행단계에서 첫 번째 코드 라인의 콘솔에서는 Reference Error가 발생합니다. 이처럼 선언 라인 이전에 식별자를 참조할 수 없는 구역을 일시적 사각지대(Temporal Dead Zone, TDZ)라고 합니다.

(2) 함수 호이스팅

2-1) 함수표현식 호이스팅

[1] var로 선언한 함수표현식

varFunctionHoisting(); // Type Error

var varFunctionHoisting = () => {
    console.log('This is Function Expression Hoisting by var')
};

함수표현식이란 함수를 변수에 담아 함수를 실행하는 방식입니다. 그리고 var로 선언한 함수표현식이란 화살표함수를 var 키워드를 통해 변수에 담아 함수를 사용할 수 있게 만든 구조입니다.

자바스크립트 작동방식에 의하면 생성단계에서 varFunctionHoisting라는 변수명과 그 값이 undefined로 초기화된 변수가 환경 레코드에 기록됩니다. 실행단계에서는 순차적으로 코드를 실행하면서 첫번째 코드 라인에서 varFunctionHoisting라는 함수를 호출하지만 환경 레코드에 기록된 값은 함수가 아닌 undefined의 값으로 기록되어 있기 때문에 Type Error가 발생하게 됩니다. 여기서 varFunctionHoistingundefined라는 변수 호이스팅은 일어났지만, 함수 호이스팅이 일어나지 않았습니다. varFunctionHoisting라는 변수에 할당된 함수 객체가 호이스팅 대상이 아니기 때문입니다.

[2] let, const로 선언한 함수표현식

constFunctionHoisting(); // Reference Error

const constFunctionHoisting = () => {
    console.log('This is Function Expression Hoisting by const')
};

let, const로 선언한 함수표현식도 마찬가지로 변수 호이스팅이 발생하지만, 함수 호이스팅이 일어나지 않습니다. 생성단계에서 let, const로 선언한 constFunctionHoisting라는 변수명만 환경 레코드에 저장되며, 초기화하지 않기 때문에 변수에 할당된 값은 아무것도 없게 됩니다. 그리고 실행단계에서 첫번째 코드 라인의 constFunctionHoisting라는 함수를 호출하는 실행을 하게되면, 환경 레코드에 저장된 아무런 값도 참조하지 않은 변수를 실행하기 때문에 Reference Error가 발생하게 됩니다.

2-2) 함수선언식 호이스팅

functionDeclarationHoisting(); // This is Function Declaration Hoisting

function functionDeclarationHoisting() {
    console.log('This is Function Declaration Hoisting')
};

함수선언식이란 function 키워드를 사용하여 함수를 선언하는 방식을 의미합니다. 위의 함수선언식 코드를 실행하게 되면 먼저 생성단계에서 선언과 동시에 functionDeclarationHoisting라는 완성된 함수 객체가 환경 레코드에 기록됩니다. 실행 단계에서 첫 번째 코드 라인의 함수 호출은 환경 레코드에 기록된 functionDeclarationHoisting 함수 객체를 호출하게 됩니다. 즉, 함수선언식은 함수 선언 이전에 실행이 가능한 함수 호이스팅이 발생하게 됩니다.

(3) 클래스 호이스팅

[1] var로 선언한 클래스표현식

let myName = new Name('YUN'); // Type Error

console.log(myName);

var Name = class {
    constructor(name) {
        this.name = name;    
    }

}

클래스도 함수표현식처럼 자신을 변수에 담아 사용할 수 있습니다.

위의 var로 선언한 클래스표현식의 자바스크립트 작동방식은 다음과 같습니다. 먼저 생성단계에서 myNameName이라는 변수명과 Name의 값으로 undefined로 초기화된 값을 가진 채로 환경 레코드에 기록합니다. 그리고 실행단계에서는 첫 번째 코드 라인의 myName 변수의 값을 new Name('YUN')으로 할당하게 됩니다. 그리고 생성자 함수로 실행된 Name('YUN')Name을 환경 레코드에서 가져올 때 함수가 아닌 undefined를 가져오기 때문에 Type Error가 발생하게 됩니다.

이는 함수표현식과 마찬가지로 Name이란 변수의 값이 undefined로 실행되면서 변수 호이스팅은 일어났지만, 클래스 호이스팅이 일어나지 않았다고 할수 있습니다.

[2] let, const로 선언한 클래스표현식

let myName = new Name('YUN'); // Reference Error

console.log(myName);

let Name = class {
    constructor(name) {
        this.name = name;    
    }

}

let, const로 선언한 클래스표현식도 let, const로 선언한 함수표현식와 같은 원리로 클래스 호이스팅이 일어나지 않습니다. 또한 생성단계에서 환경 레코드에 저장된 Name 변수의 값은 없고 어떠한 값도 참조하지 않기 때문에 실행단계에서 실행한 첫 번째 코드 라인에서의 Name을 호출하면, 어떠한 값도 참조하지 않는 Reference Error를 발생시키게 됩니다.

[3] 클래스선언식

const Name = '';

{
  	// 호이스팅이 일어나지 않았다면 ''이 출력되어야 한다.
    console.log(Name); // ReferenceError: Cannot access 'Name' before initialization

    class Name {
        constructor(name) {
            this.name = name;    
        }
    
    }
}

클래스선언식이란 class 키워드를 사용하여 클래스를 선언하는 방식을 의미합니다. 위의 작성한 클래스선언문은 생성단계에서 선언문을 실행할때, class 키워드로 선언한 Name이란 객체는 선언과 동시에 uninitialized의 값으로 초기화된 상태로 환경 레코드에 기록됩니다. 이는 실행단계에서 Name을 콘솔로 호출하게 될 때 uninitialized과 비교하여 Reference Error를 발생시키게 됩니다. 이는 let, const의 변수 호이스팅처럼 일시적 사각지대에 영향을 받아 호이스팅이 발생하지 않은 것처럼 보이나 실제론 호이스팅이 발생한 현상입니다.

4. 호이스팅을 알아야 하는 이유

제일 중요한 내용입니다. 우리는 왜 호이스팅을 알아야 할까요?

자바스크립트 엔진의 동작방식을 보았을 때 실행순서가 단순한 런타임이 아닙니다. 자바스크립트의 동작방식과 함께 복잡하고 다양한 호이스팅 현상을 제대로 이해해야 비로소 자바스크립트의 코드를 원하는대로 작성할 수 있습니다. 호이스팅에 의한 잠재적 오류를 최소화하기 위해 변수 선언을 var로 사용하지 않으며, 함수선언식보단 함수표현식을, 클래스선언식보단 클래스표현식을 사용하는 것을 권장합니다.

5. 정리

- 호이스팅이란 선언문이 마치 최상단에 끌어올린 듯한 현상
- var 변수 호이스팅에선 선언과 초기화가 동시에 진행되어 호이스팅 결과로 undefined 값이 출력
- let, const 변수 호이스팅에선 선언만 진행되어 할당문에는 값이 참조되지 않아 Reference Error 발생
- let 또는 const로 선언했을 때, 선언 이전에 식별자를 참조할 수 없는 구역을 일시적 사각지대(Temporal Dead Zone, TDZ)이라 함
- 함수표현식은 함수를 변수에 담고 있으므로 변수 호이스팅과 동일하게 작동
- 함수표현식은 함수 객체가 호이스팅의 대상이 아닌 변수에 대한 호이스팅만 일어나기 때문에 이때 함수 호이스팅이 일어나지 않았다고 표현하는 것이 적절
- 함수선언식은 선언과 동시에 완성된 함수 객체를 생성하여 호이스팅 결과로 함수 객체 값이 그대로 출력
- 클래스표현식은 함수표현식처럼 클래스 호이스팅이 일어나지 않지만, 클래스선언식은 let, const 키워드로 선언한 변수처럼 호이스팅된다.
profile
https://github.com/KANGPUNGYUN

0개의 댓글