호이스팅(Hoisting) 개념 정리 – JavaScript

호이스팅은 자바스크립트를 사용하는 개발자라면 알고 있어야 할 개념 중 하나입니다.

앞서 포스팅한 스코프(Scope)를 어느 정도 알고 있어야 설명하기 좋습니다.

scope에 대한 이해도가 있다면 hoisting은 쉽게 이해할 수 있습니다.

호이스팅까지 이해한다면 JavaScript의 독특한 특징을 이해한다고 할 수 있습니다.

호이스팅이란?

hoisting은 끌어올린다는 뜻으로 함수 내의 변수 및 함수 선언을 각 유효 범위의 최상단으로 끌어 올려주는 JS의 독특한 특징입니다.

실제로 코드를 끌어올리는 것은 아닙니다. 다만, 자바스크립트 parser가 내부적으로 끌어올려서 처리합니다. 컴파일 단계에서 코드 실행 전 함수와 변수 선언을 확인하고, 모든 선언 들을 렉시컬 환경이라 불리는 자바스크립트 데이터 구조내의 메모리에 추가 됩니다.

console.log(a); // a 선언이 뒤에 있기 때문에 undefined가 나옵니다.
var a = 'a';
/* 호이스팅 된 코드입니다. */
var a;
console.log(a); // a가 출력됩니다.
a = 'a';

처음 있는 코드가 우리가 작성한 본래 코드입니다. 컴파일 단계에서 먼저 선언했던 함수와 변수를 위에 올립니다. 그렇게 되면 두 번째 코드처럼 되는 것이죠. 이렇게 되면 첫 번쨰 코드처럼 오류가 발생하지 않고, 원하는 값이 나오게 됩니다.

이것을 우리는 호이스팅이라고 합니다.

렉시컬 환경

앞서 이야기한 렉시컬 환경은 클로져와 스코프 글에서 자세히 설명했지만 간단하게 이야기하면 아래와 같습니다.

Lexical Environmanet(렉시컬 환경)은 “identifier-variable” 매핑 정보를 가지고 있는 데이터 구조이다. “identifier”란 변수, 함수의 이름을 뜻합니다. “variable”은 실제 객체 혹은 원시값을 이야기합니다.

Sukhjinder Arora ‘s Medium

이것을 코드로 나타내면 아래와 같습니다.

LexicalEnvironment = {
  Identifier: value,
  Identifier: function object
}

호이스팅 규칙

호이스팅은 크게 함수 호이스팅과 변수 호이스팅으로 나뉩니다. 그리고 해당 개념에 관한 규칙을 요약하면 다음과 같은 항목으로 이루어집니다.

  1. 선언된 함수는 상단에서 참조, 호출이 가능합니다.
  2. 선언된 var 는 상단에서 참조, 할당이 가능합니다.
  3. 선언된 let , const 는 상단에서 참조, 할당이 불가능합니다.

이 3가지 규칙을 찬찬히 읽어보고 밑에서 다루는 각 항목의 상세 설명을 확인하길 바랍니다.

자바스크립트의 변수 생성 단계

자바스크립트는 총 3단계에 걸쳐 변수를 생성합니다.

  • 선언(Declaration): 스코프와 변수 객체가 생성되고, 스포크가 변수 객체를 참고합니다. 초기화 전까지는 TDZ(Temporal Dead Zone) 상태입니다. 즉, 일시적인 사각지대입니다. 이 상태는 객체는 스코프가 참조하는 대상이 됩니다.
  • 초기화(Initialization): 실행 컨텍스트에 존재하는 변수 객체에 선언 단계의 변수를 위한 메모리를 만드는 단계입니다. 즉, 변수 객체 값을 위한 공간을 메모리에 할당합니다. 이 때 할당 값은 우리가 아는 undefined 값입니다.
  • 할당(Assignment): 변수 객체에 값을 할당합니다. undefined 값에 우리가 선언한 값을 집어 넣습니다.

이 단계를 알아야 호이스팅을 이해할 수 있습니다. 그리고 let/const의 동작 원리도 이해할 수 있죠.

var, let, const

간단하게 각 문법의 특징을 이야기하겠습니다.

var는 선언과 동시에 초기화가 이루어집니다. 그렇다면 ES6에 나온 let과 const도 동일하게 이뤄질까요?

먼저 모든 선언(function, var, let, const, class)은 JavaScript에서 호이스팅이 됩니다. 그 이후가 다르게 동작하는데요.

var 선언은 undefined로 초기화되지만 let 및 const 선언은 초기화되지 않은 상태로 유지됩니다.

이것의 차이는 아래 코드를 보면 알 수 있습니다.

console.log(a); // undefined
var a = 'abc';
console.log(b); // ReferenceError: b is not defined
let b = 'abc';

이렇듯 var는 undefined로 초기화 됩니다. let은 초기화가 되지 않은 상태라 참조 자체를 찾지 못합니다. 물론 const도 마찬가지입니다.

결론적으로 var, let, const는 전부 호이스팅이 됩니다. 다만, 초기화 부분에서 차이가 있는 것 뿐이죠.

좀 더 확실한 코드를 보자면 아래 코드를 보면 알 수 있습니다.

var h = 'AA';

(function func() {
  console.log(h);  // ReferenceError
  let h = 'BB';
})();

호이스팅이 되지 않는다면 AA가 호출되어야 하지만 호이스팅으로 인해 Reference Error가 발생합니다.

함수 선언식(Function declarations)과 함수 표현식(Function Expressions)

함수 선언식은 호이스팅이 됩니다. 하지만 함수 표현식은 호이스팅이 되지 않습니다. 물론 함수 선언식을 조합한 함수 표현식도 호이스팅이 되지 않습니다. 코드를 보면 빠르게 이해할 수 있습니다.

func(); // function is hoisting

function func() {
  console.log('functon is hoisting');
};

express(); // TypeError

var express = function() {
  console.log('function is hoisting');
}

express2(); // TypeError

var express2 = function func() {
  console.log('function is hoisting');
}

Class 선언식(declarations)과 Class 표현식(expressions)

자바스크립트의 class 선언식은 호이스팅이 되고, 초기화도 되지 않는 TDZ 상태로 유지 됩니다. 그래서 Class에 접근하기 위해서는 선언을 먼저 해야합니다. 선언을 하지 않으면 ReferenceError가 발생합니다.

Class 표현식은 앞서 말한 함수 표현식처럼 호이스팅이 되지 않습니다.

var CHoist = new Hoisting();
console.log(CHoist); // ReferenceError

class Hoisting {
  constructor() {};
}

var CHoist = new Hoisting();
console.log(CHoist); // TypeError

var Class = class Hoisting {
  constructor() {}
};

선언식들은 호이스팅이 되고, 표현식들은 호이스팅이 되지 않습니다.

마무리

호이스팅은 변수나 함수, 클래스를 어디에 선언해도 필요한 곳에서 자유롭게 사용하기 위해 만들어진 기능입니다. 물론 이런 특징이 익숙하지 않은 개발자는 의도치 않게 버그를 만날 수 있다. 자유도가 높기 때문에 잘 알고 사용해야 한다. 그래서 호이스팅을 사용하지 않기 위해서는 TDZ로 초기화 되는 let이나 const, 함수 표현식, 클래스 표현식을 사용해야 합니다.

참고 링크

https://hanamon.kr/javascript-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85%EC%9D%B4%EB%9E%80-hoisting/

https://velog.io/@rkio/Javascript-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85hoisting%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC

https://velog.io/@1nthek/JavaScript-%EB%B3%80%EC%88%98%EC%99%80-%ED%95%A8%EC%88%98-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85Hoisting%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

Leave a Comment