어서와, 개발은 처음이지?

자바스크립트 호이스팅과 스코프 본문

[기획] 누구도 알려주지 않은 이야기

자바스크립트 호이스팅과 스코프

오지고지리고알파고포켓몬고 2019. 10. 12. 18:11
반응형



이번 글에서는 자바스크립트의 호이스팅(hoisting) 현상을 스코프 관점에서 이해해보도록 하겠습니다.

호이스팅 현상에 대해 좀 더 깊게 이해하기 위해, 본 글을 읽기 전에 자바스크립트 스코프 를 읽고 오시길 권장드립니다.


1. var 변수의 의도치 않은 현상


이전에 작성한 스코프(scope) 글을 보고 눈치 채셨거나, 이미 아시는분들도 계시겠지만 javascript에서 var 선언문을 사용하여 변수를 선언하면 아래와 같은 상황이 발생할 수 있습니다.

if(true){
  var name = 'yuddomack';
}
console.log(name); // yuddomack
for(var i=0; i<5; i++){
  // do something
}
console.log(i); // 5


javascript가 아닌 c나 java같은 프로그래밍 언어를 사용하셨다면 console.log에 값이 찍혀 나오는 것은 이해할 수 없는 동작입니다.

위 언어들은 변수가 블록 스코프에서 유효하기 때문에, if 블록 혹은 for 블록 안에서 생성된 변수를 밖에서 참조할 수 없습니다.


하지만 javascript에서 이러한 동작이 가능한 이유는 바로 호이스팅(hoisting) 때문인데요.

지금부터 호이스팅 현상을 탐구해보도록 하겠습니다.



2. 호이스팅이란?


호이스팅을 한 줄로 설명하자면, 선언문을 유효 범위의 최상단으로 끌어올리는 행위라고 할 수 있습니다.

'선언과 할당의 분리'라고 생각하면 기억하기 좋을 것 같습니다.

if(true){
  var name = 'yuddomack';
}
console.log(name);
f();
funcion f(){
  console.log('hi");
}

<개발자가 작성한 코드>

위 코드는 컴파일레이션 과정에 의해 호이스팅이 발생하여 실제로는 아래 형태로 작성한 코드처럼 해석되게 됩니다.

/* 선언 */
var name; // 선언문이 유효범위 최상단으로 끌어올려짐
function f(){ // 함수 선언 또한 마찬가지
  console.log("hi");
}

/* 실행 */
if(true){
  name = 'yuddomack'; // 할당
}
console.log(name);
f();

<호이스팅 발생으로 위 코드처럼 해석>

때문에 if 블록 밖에서도 name 변수를 참조할 수 있고, 함수 선언 전에 함수 호출이 가능하게 보이는 것 입니다.



3. 엔진 입장에서 이해해보자


좋습니다.

이제 호이스팅이라는게 있다는 것도 알겠고, 호이스팅이 뭔지도 설명할 수 있을 것 같습니다.


그럼 호이스팅이 어떻게 가능한걸까요?


아래 과정들을 통해 컴파일레이션과 스코프에 대한 내용을 다시 한번 상기해 보겠습니다.



<렉싱>

우선 좌측 코드는 컴파일레이션 과정 중 렉싱을 통해 오른쪽 수도 코드처럼 의미있는 조각(token)으로 나누어집니다.




<스코프 생성>

 다음은 렉싱과정에서 생성된 조각에 근거하여 컴파일러가 파싱, (컴퓨터가 이해할 수 있는)코드 생성을 수행하고, 스코프는 변수 목록을 작성합니다.


scope 함수가 호출되기 때문에 함수의 스코프 영역이 생성되고, if 조건이 true이기 때문에, 변수 c 또한 함수 스코프에 생성되게 됩니다.


눈치 빠른분들은 여기서 헛! 😳 하셨을 것 같습니다. 

그럼 여기에서 log를 찍는 코드를 추가해보도록 하겠습니다.




<코드 실행 시점>


좌측 코드에 4개의 console.log를 추가했습니다. 스코프 console.log과 상관없이 같은 형태로 생성되어 있습니다.


이윽고 runtime 시점이 다가오면 스코프에서 RHS 탐색으로 console.log의 인자들을 탐색하게 됩니다.


위에 언급한대로 runtime 시점에서 스코프는 이미 생성되어있는 시점이기 때문에, 값 할당 여부와 상관없이 RHS 탐색으로 변수를 찾아낼 수 있습니다.(가장 가까운 스코프에 있는 변수를 참고합니다. 같은 변수 이름을 사용했을때 결과가 헷갈리시는 분은 이전 글을 참고해주세요)


실제 실행시에는 LHS 탐색을 반영하여 아래와 같은 결과가 나오게 됩니다.




<실행 결과> 

고로 2번 섹션에서 언급한 것 처럼 아래와 같은 코드와 동일하게 해석된다고 볼 수 있습니다.

var a;
var b;
function scope(){
    var b;
    var c;
    b = 50;
    if(true){
        c = 60;
    }
    console.log(b); // 50
    console.log(c); // 60
}
console.log(b); // undefined
a = 10;
b = 20;
console.log(b); // 20

scope();




4. let, const


그렇다면 블록스코프에서 동작하는 let이나 const 선언문은 어떨까요?

console.log(a); // ReferenceError: a is not defined
let a = 10;

깔끔하게 참조 에러가 발생합니다.

이들이 우리 기대처럼 동작하는 이유는 호이스팅이 일어나지 않기 때문일까요?


결론부터 이야기하자면 let, const도 호이스팅이 일어납니다. 다만 이들은 초기화 되기전까지 temporal dead zone에 머물게 구현되었습니다.(v8 엔진을 열어서 확인해보고싶지만 아직 부족하여..)


temporal dead zone은 변수가 생명(초기화/할당)을 얻기 전에 잠시 죽어있는 공간이라고 생각하시면 좋을 것 같습니다.


이번에도 그림을 보며 이야기 해보겠습니다.


<생명을 얻기 전 let의 모습>

위에 언급한대로 let 변수도 호이스팅이 일어나지만, 초기화 되기 전까지 죽어있는 상태이기(temporal dead zone에 머물기) 때문에 참조가 불가합니다.




<생명을 얻은 let의 모습>

다음으로 초기화 구문이 실행되면 temporal dead zone을 벗어나 참조할 수 있는 상태가 됩니다.




<이제는 참조할 수 있다>

이제 스코프 내에서 let a를 탐색할 수 있게 됩니다.



5. 코드로 보자


이렇게 설명만 봤을땐 let, const가 호이스팅 된다는 사실이 뭔가 미덥습니다.


이번엔 코드를 통해 확인해보겠습니다.

let a = 10;
let b = 20;

{
    console.log(a); // 10
}

{
    console.log(b); // ReferenceError: b is not defined
    let b = 50; 
    console.log(b);
}

위 코드를 실행하면 a는 정상 출력되지만 b는 참조 에러가 발생합니다.


let, const가 블록 스코프 내에서 동작한다는 점을 다시 한번 상기하자면,

만약 호이스팅이 발생하지 않았다면 RHS 탐색에 의해서 첫번째 log(b)는 20이 출력되고, 이후부터는 블록 내에서 선언된 50이 출력 될 것입니다.


하지만 블록 내에서 호이스팅이 발생하기 때문에 temporal time zone에 들어간 (50으로 초기화 될)b를 찾기 때문에 참조 에러가 발생합니다.



6. 결론


여기까지 자바스크립트의 호이스팅 현상의 발생 원인에 대해서 스코프의 관점으로 알아보는 시간을 가졌습니다.


여기서 우리는 var, let, const 모두 각자 유효 스코프 내에서 호이스팅이 발생하며, let과 const는 값이 초기화/선언 되기까지 temporal time zone이라는 곳에 위치하여 에러를 발생시키고 때문에 (var과 비교하자면)정상적인 동작한다는 것을 알게 됐습니다.


이번 글이 '호이스팅은 이러이러한 현상이다'에서 '호이스팅은 이러이러한 현상이고 이러이런 이유로 일어나게 된다' 원리 이해에 도움이 되셨길 바라겠습니다.


이외에 이번 글에서 자세히 다루지 않은 함수 호이스팅에 대한 내용은 예제를 넣어 작성한 포스팅에서 참고하시면 도움이 될 것 같습니다.

Comments