호이스팅이란?

hoist 는 끌어올린다는 뜻의 영어 단어입니다. JavaScript 에서의 hoisting 역시 이와 비슷한 의미로 쓰입니다. 오늘은 JavaScript에서 hoisting 이 어떻게, 왜 돌아가는지 확인해 보겠습니다.

hoisting: JavaScript 가 변수를 선언하는 법

먼저 변수를 선언하려면 변수를 선언하는 코드가 실행되어야겠죠? JavaScript 의 모든 코드는 실행 컨텍스트 위에서 동작합니다. 실행 컨텍스트는 코드의 실행에 필요한 정보가 모여 있는 레이어와 같은 개념입니다.

아무튼 컴파일 언어인 JavaScript 는 코드를 실행하기 전에 실행 컨텍스트에 변수가 생길 자리를 미리 만들어 둡니다.

가령 아래와 같은 코드를 보겠습니다.

var v = 1;
console.log(v); // 1

let l = 2;
console.log(v + l); // 3

const c = 3;
console.log(v + l + c); // 6

위 코드를 실행하면, JavaScript 는 코드를 실행하기 전에 실행 컨텍스트에 vlc 변수를 먼저 선언해 둡니다. 그리고 코드를 실행하며 v에 1을, l에 2를, c 에 3을 할당하는 거죠.

그러니까 코드의 실행 전에, 선언들을 모아서 먼저 정의해둡니다. 할당은 나중에 하더라도요.
그러면 우리 눈에는 마치 아래 코드처럼 동작하는 것처럼 보이겠죠?

var v;
let l;
const c;

v = 1;
console.log(v); // 1

l = 2;
console.log(v + l); // 3

c = 3;
console.log(v + l + c); // 6

그냥 이런 것 처럼 보인다는 뜻이고, 실제로 의미가 있는 코드는 아닙니다.

마치 변수의 선언부가 코드의 위쪽으로 끌어올려진 것 같네요. 변수뿐 아니라 함수 선언 (function) 도 이와 같이 "끌어올려"진 것처럼 보이게 됩니다.

이게 바로 hoisting 입니다.

브랜든 아이크는 왜 다른 언어들처럼 코드가 실행되는 동안 Stack 에 변수를 선언하지 않고 Stack 에 실행 컨텍스트를 쌓은 다음 그 안에 변수를 미리 선언하는 방식을 택했을까요?



var 의 호이스팅

var 키워드는 ES5까지 JavaScript 에서 변수를 선언하는 방식이었습니다. var 은 호이스팅될 때 (컴파일 타임에) 값이 undefined초기화됩니다. 이런 var 과 호이스팅의 궁합은 여러 가지 문제가 있었습니다. 기본적으로 일반적인 언어들과 다르다는 점도 있었고, 아래 코드를 볼까요?

console.log(x); // undefined
var x = 'Hello World!';

x'Hello World!' 를 넣으려고 했을 텐데 undefined 가 나왔네요. 일리가 있는 코드는 아니라고 보여집니다. 하나 더 볼까요?

var x = 1;

function fn () {
  console.log(x); // undefined
  var x = 2;
}

fn();

1도 아니고 2도 아니고 undefined 가 출력됩니다. global execution context 에 x 라는 변수가 선언되었고 1 이 할당되어 있긴 하지만, fn 안에도 x 라는 변수가 선언되었고 undefined 가 할당되어 있으니까요. 스코프 체이닝에 따라 현재 실행 컨텍스트에 변수가 선언되어 있다면 더 위의 스코프는 확인하지 않습니다.

이를 shadowing 이라고 합니다.

JavaScript 의 특성상 이렇게 함수가 스코프 체이닝을 통해 외부 스코프의 값을 참조하는 일은 아주 빈번한데, 이런 문법들마다 실행 컨텍스트에 변수가 미리 선언되는 것을 고려해야 하는 것이죠. 유지보수에 걸림돌이 되고, 다른 언어 출신의 개발자들에게 소위 "기괴함" 을 느끼게 합니다.

var은 호이스팅될 때 값을 undefined 로 초기화함으로써 스스로 문제가 되었습니다. JavaScript 에서 undefined 는 하나의 값인 것처럼 사용되어 있었기 때문에 더 이상해졌죠. 조금 주제넘지만 아마 JavaScript 설계상의 몇 가지 실수 중 하나가 아니었나 싶습니다.


letconst 의 호이스팅

아마도 이런 기괴한 동작을 해결하기 위해, ES6 에서 letconst 라는 문법이 등장하게 됩니다. 이후 대부분의 JavaScript 코드에서는 린터도 있을 정도로 var을 사용하는 것을 지양하고 있습니다.

letconst 역시 호이스팅이 안 되는 것은 아닙니다. 다만 이들은 컴파일 타임에 값이 undefined 로 초기화되지 않습니다.

그냥 컴파일 타임에 초기화 자체가 되지 않으며, 대신 실행 중 초기화가 되는 변수입니다. 따라서 이 변수를 초기화 전에 사용하려 하면 ReferenceError 가 뜨게 됩니다.

아까 그 코드를 그대로 let 으로 바꿔 볼까요?

let x = 1;

function fn () {
  console.log(x); // Uncaught ReferenceError: Cannot access 'x' before initialization
  let x = 2;
}

fn();

shadowing도 똑같이 일어나고, 호이스팅도 똑같이 일어나지만 이제는 ReferenceError 가 뜨게 됩니다.

이렇게 호이스팅이 되어 선언은 되었으나 참조하지 못하는 영역을 TDZ 라고 합니다.

아래 fn 함수 내에서 x의 선언이 호이스팅되기 때문에 위에 1을 선언한 건 shadowing 되어 무시되고, x를 출력하는 코드의 입장에서는 x 가 선언은 되었는데 아직 초기화가 되지 않았기 때문에 에러가 발생합니다.

그래서 기존 var 이었으면 오류 없이 잘 되어 잠재적인 문제를 만들 수 있었던 많은 '기괴한' 것들이 let 으로 선언되었다면 오류가 발생하게 됩니다.

const의 경우에도 호이스팅의 관점에서는 let과 동일합니다. 다만 const 는 한 번 초기화되면 값을 바꿀 수 없죠. 호이스팅의 관점에서는 동일하니 따로 다루지는 않겠습니다 :)



흔한 오해

  1. var 은 호이스팅되고 constlet 은 호이스팅되지 않는다?
    • 모두 호이스팅은 됩니다. 다만 컴파일 타임에 undefined로 초기화되냐 / 컴파일 타임에 초기화되지 않냐의 차이입니다.
  2. 호이스팅은 선언부를 끌어올리는 것이다?
    • JavaScript 컴파일러가 진짜로 선언 코드를 끌어올려 코드를 다시 작성하는 게 아니라, 코드를 쭉 읽어보고 선언을 미리 실행 컨텍스트에 해두는 것입니다. 때문에 프로그래머 입장에서는 선언을 끌어올려서 하는 것처럼 보이게 됩니다.



출처

좋은 웹페이지 즐겨찾기