JavaScript - hoisting

김민기·2022년 9월 3일
0

JavaScript-Study

목록 보기
7/12

호이 호이

호이스팅

자바스크립트를 처음 공부할 때 어렵다고 들었고 면접 단골 질문으로 알고 있는 호이스팅에 대해 알아본다

JavaScript에서 호이스팅(hoisting)이란, 인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미합니다. var로 선언한 변수의 경우 호이스팅 시 undefined로 변수를 초기화합니다. 반면 let과 const로 선언한 변수의 경우 호이스팅 시 변수를 초기화하지 않습니다. [MDN]

console.log(num); // undefined

var num = 10;

일반적으로 코드는 위에서 아래로 읽혀나간다. 따라서 conosole.log에서 num 변수를 출력하려면 그 윗줄에 num이라는 변수가 있어야 한다. 하지만 num은 console.log 라인 아래에 선언되어 있다. 게다가 이 코드는 정상적으로 동작하지 않는다. 코드를 모두 읽은 상태(실행 완료)에서 보면 num은 10이라는 값을 갖고 있기 때문에 undefined라는 값은 옳은 값이 아니다.

나는 처음 이게 무슨 문제라는 거지? 어차피 undefined 나오는데 틀린거 아닌가? 라고 생각했었다...

하지만 문제는 값이 틀렸다는게 아니라 에러가 발생하지 않았다는 것이다. 참조할 수 없는 변수를 참조하고 있는데 자바스크립트는 에러로 처리하지 않는다. 자바스크립트에서 undefined는 원시 타입의 값이다.(primitive value) 따라서 undefined이더라도 을 출력하고 있다는 점이다.

처음 배울때 가장 오해하기 좋은 점이 var 키워드를 사용하면 호이스팅이 발생해서 쓰지 말아야하고 let, const 키워드를 사용해야 한다는 것이다.

하지만 알고보면 let, const도 호이스팅이 발생하지만 발생하지 않는 것처럼 보일 뿐이고, 위에서 작성한 예제처럼 변수만 호이스팅 되는 것이 아니라 함수도 호이스팅 된다는 것이다!

변수, 함수 호이스팅

그렇다면 호이스팅이란 무엇인가? 단어의 의미대로 끌어올리기, 들어올리기라는게 어떤 의미를 갖고 있는가
변수, 함수 모두 코드의 상단으로 끌어 올려진다는 말이 무슨 말일까

선언문에 대해 얼마나 알고 있나요

이전에 함수에 대해서 정리할 때, 함수 선언문은 런타임 이전에 자바스크립트 엔진이 함수 객체를 생성하고 함수 선언문의 이름과 동일한 식별자를 암묵적으로 생성하고 생성된 함수 객체를 할당한다고 했었다.

함수 선언문으로 정의한 함수는 함수 선언문 이전에 호출할 수 있다.(호이스팅!)

여기서 중요한것은 선언문은 런타임 이전에 자바스크립트 엔진이 해석한다는 것이다. 변수 선언문, 함수 선언문도 마찬가지다.

var num;

function num3() {...}

자바스크립트 엔진은 변수 선언을 다음과 같은 2단계에 거쳐 수행한다.

  • 선언 단계: 변수 이름을 등록해서 자바스크립트 엔진에 변수의 존재를 알린다.
  • 초기화 단계: 값을 저장하기 위한 메모리 공간을 확보하고 암묵적으로 undefined를 할당해 초기화한다.

var, let은 무슨 차이

var는 변수 중복 선언이 가능하고 let은 불가능하다 이런 얘기보다 앞에서 말한 자바스크립트 엔진의 동작을 중점으로 차이점을 확인해보면 var는 자바스크립트 엔진이 암묵적으로 선언 단계와 초기화 단계가 동시에 진행된다는 것이고 let은 선언문의 특성상 선언 단계는 먼저 실행되지만 초기화 단계는 변수 선언문에 도달했을 때 실행된다는 것이다.

varlet
변수 선언문이 자바스크립트 엔진에 의해 선언과 동시에 초기화 된다.변수 선언과 초기화 단계를 구분한다.

let의 경우 선언 단계와 초기화 단계(런타임에서 변수 선언문을 실행할 때) 사이에 일시적 사각지대가 발생한다.(TDZ, Temporal Dead Zone) 이 사각지대에서 변수를 참조하려고 한다면 ReferenceError를 발생시킨다.

let 키워드로 선언한 변수가 호이스팅이 발생한다는 것을 보여주는 예제

let foo = 1;

{
  console.log(foo);// Reference Error
  let foo = 2;
}

코드 블록 내부에 있는 콘솔에서 foo를 출력할 때 Reference Error 가 발생했다. 만약 호이스팅이 발생하지 않는다면, 콘솔은 전역 변수 foo의 값 1을 출력해야 한다.
그렇다면 왜 출력되지 않았는가? 스코프의 내용을 조금 첨가하면, 전역 스코프에서 foo 변수가 선언되었고 1번 라인에서 초기화 되었다. 그리고 블록 스코프에서 console.log(foo)를 실행 할때, 스코프에 foo라는 변수가 없다. 따라서 스코프 체이닝에 의해 상위 스코프인 전역 스코프에서 foo를 찾는다. foo가 있기 때문에 1을 출력할 수 있다.
호이스팅이 발생한다면. 블록 스코프 내부에서 foo라는 변수는 호이스팅되어 런타임 이전에 선언된 상태다. 하지만 let으로 선언했기 때문에 초기화 단계 (let foo = 2;)를 실행하기 전에는 참조할 수 없다. 따라서 Reference Error가 출력된다.
Reference Error가 발생했기 때문에 우리는 let 또한 호이스팅 된다는 것을 알 수 있다.

정리

자바스크립트에서 선언문(변수 선언문, 함수 선언문 등)은 런타임 이전에 자바스크립트 엔진에 의해 해석된다.

왜 이렇게 만들었는지는 만드신 분이 잘 아실거 같기는 한데 한번 찾아봐야겠다.
변수의 경우 var 키워드를 사용하면 자바스크립트 엔진이 알아서 선언부터 undefined로 초기화까지 한번에 끝내버린다. 따라서 var 키워드로 선언한 변수를 런타임에서 실행하기 전에 참조해도 에러를 발생시키지 않고 undefined라는 값을 출력해준다.
이러한 문제점을 보완하기 위해서 let, const 키워드가 추가되었다. 자바스크립트 엔진이 let, const 키워드를 만나면 (여기서 const는 어차피 선언과 동시에 초기화 해야하기 때문에 상관 없다.) 선언과 초기화 단계를 구분시켜서 선언 단계만 실행하고 초기화 단계는 실제 런타임에서 그 코드에 도달했을 때 실행하도록 만든다. 따라서 초기화 단계 이전에 변수에 접근하려고 하면 참조 에러를 발생시킨다!

0개의 댓글