goongoguma's blog

Understanding Hoisting in JavaScript

여러분은 자바스크립트에 알고있습니다만, 정말 자바스크립트를 아시나요? 다른 의견이 있을것 같지만 자바스크립트는 훌륭한 언어라고 할 수 있습니다. 물론, 단점도 존재합니다. 그러나 지난 몇년사이에 자바스크립트는 많이 발전해왔고 개발자들도 자바스크립트를 올바르게 사용하는데 익숙해지고있습니다. Strict 모드는 또한 새로운 개발자들이 자바스크립트를 다룰때 실수함으로써 예상치 못한 동작을 방지하는데 있어 향상되고 있습니다.

그러나, 모든 사람들이 호이스팅에 관해 들어보거나 어떤 뜻인지 알고있지는 않습니다. 이 글에서 호이스팅을 여러 예시와 함께 설명해드리겠습니다.

The JavaScript Interpreter

자바스크립트 코드를 실행시킬 때, 인터프리터(interpreter)는 코드를 두번 훓어봅니다.

처음에는 코드가 안전한지 체크를 하고 작은 부분에 있어서 최적화를 할 수 있는지 훓어봅니다. 안전 체크라는 말은 구문이 올바르게 작성되었는지 혹은 eval이나 with가 쓰였는지 등등 체크하는 것을 뜻합니다. 그리고나서, 인터프리터는 코드가 실행되었을때 더 나은 퍼포먼스를 위해 최선을 다해 성능 최적화를 실행합니다. 그리고 이 부분에서 호이스팅이 발생합니다. 이 부분은 또한 컴파일런(compile run)이라고 합니다.

두번째 훓어볼때, 코드를 라인 하나하나 실행을 하며, 할당을 하고, 함수를 실행하는 과정을 거칩니다.

What is Hoisting?

호이스팅이란 자바스크립트 인터프리터가 모든 변수와 함수 선언을 현재 스코프의 맨 위로 올렸을때 발생합니다. 실제적으로 선언이된 부분들만이 호이스팅이되며 할당된 부분들은 그 자리에 남아있다는것은 굉장히 중요합니다.

Variable Declarations

아래의 코드를 보며 기본부터 시작해보겠습니다.

"use strict";
console.log(bar);
var bar = "bar";
console.log(bar);

맨 처음에 여러분은 아마 위의 예시가 맨 위의 console.log(bar) 부분에서 ReferenceError가 발생할것이라 생각했을수도 있습니다. 왜냐하면 bar는 아직 선언되지도 않았거든요. 하지만 호이스팅덕분에 에러를 발생시키지 않고 대신 undefined가 콘솔에 찍히게 됩니다. 이것은 자바스크립트 인터프리터가 코드를 처음 훓어보면서 모든 변수와 함수 선언들을 현재 스코프의 맨 위로 끌어올렸기 때문에 발생한것이라 할 수 있습니다. 그리고 두번째 실행이 됐을때 해당 코드들을 실행시킵니다.

아래의 예제는 인터프리터가 처음 실행되었을때의 코드와 같은 코드입니다.

"use strict";
var bar;
console.log(bar); // undefined
bar = "bar";
console.log(bar); // 'bar'

보시다시피 변수 bar가 맨 위에서 선언되었지만 해당 단계에서 값 할당이 안된게 보이시나요? 미묘하지만 중요한 차이점이 있으며 왜 bar가 콘솔에서 ReferenceError 대신에 undefined를 보여주는 이유이기도 합니다.

Function Declarations

호이스팅은 함수 선언문에서도 적용됩니다. 아래의 코드를 분석해 보도록 하겠습니다.

"use strict";

foo();
function foo() {
  console.log(bam); // undefined
  var bam = "bam";
}

console.log(bam);

위의 예시에서, 함수 foo를 성공적으로 호출했습니다. 이것은 함수 선언문이므로 현재 스코프의 맨 위로 끌어 올려지게 됩니다. 그리고나서, 다음의 foo는 이전 예시에서와 같이 bam이 foo 함수인 현재 범위의 맨 위로 호이스팅이 되므로 호출할때 undefined를 호출합니다. 이 말은 bam은 console.log(bam) 이전에 호출이 되었지만 아직 값이 할당되지 않았다는것을 뜻합니다(bam = ‘bam’).

그러나, 중요한 점은 여기에 있습니다. bam은 현재 스코프의 맨 위로 호이스팅 됐다는것 입니다. 이 말은 전역 스코프에 선언되있는것이 아닌 함수 스코프 안에 선언되었다는것을 뜻합니다.

아래의 코드는 인터프리터가 처음 실행되었을때의 코드와 같은 코드입니다.

"use strict";

function foo() {
  var bam;
  console.log(bam); // undefined
  bam = "bam";
}

foo();
console.log(bam); // ReferenceError: bam is not defined

foo()가 제일 위로 올라갔으며 bam이 foo 함수 안에 선언된게 보이시나요? 이 뜻은, console.log(bam)을 호출했을때, 전역 스코프에서 bam을 못찾아 에러를 발생시킨다는 겁니다.

Function Expressions

세번째 예시로 함수 표현식은 함수 선언문과 다르게 호이스팅이 안되는지 설명해드리겠습니다. 함수 표현식은 호이스팅이 되지 않지만 표현식 안에 선언된 변수들은 호이스팅이 됩니다. 아래의 예시를 봐주세요.

"use strict";

foo();
var foo = function () {
  console.log(bam); // undefined
  var bam = "bam";
};

이 코드는 TypeError: foo is not a function에러를 발생시키며 변수 선언인 var foo만이 파일의 맨 위로 호이스팅 됩니다. 그리고 함수 foo의 할당은 인터프리터가 두번째 실행되었을때 발생하게 됩니다. 아래의 코드는 인터프리터가 두번째 실행되었을때와 같은 코드입니다.

"use strict";

var foo;
foo(); // `foo` has not been assigned the function yet
foo = function () {
  console.log(bam); // undefined
  var bam = "bam";
};

What Takes Precedence?

마지막으로, 제가 보여드리고 싶은 예시는 변수보다 함수 선언문이 더 위에서 호이스팅이 된다는 것입니다. 아래의 코드를 봐주세요.

"use strict";

console.log(typeof foo); // function

var foo = "foo";

function foo() {
  var bam = "bam";
  console.log(bam); // bam
}

해당 예시에서 typeof foo는 string대신에 function을 반환합니다. foo 함수가 변수보다 더 늦게 선언이 되었는데도 말이죠, 이것은 왜냐하면 함수 선언문은 변수 선언전에 호이스팅이 되기때문에 foo=’foo’는 typeof foo가 실행된 이후, 인터프리터가 두번째 실행이 되었을때 작동된다는 것입니다.

첫번째 인터프리터가 실행하면 foo() 함수는 현재 스코프에서 맨 위로 호이스팅이 되며 그 이후에 var foo = ‘foo’ 라인을 실행시킵니다. 이 과정에서 foo는 이미 선언이 되었기에 인터프리터는 더 이상 아무것도 하지 않고 첫번째 과정을 계속 진행합니다.

그리고 두번째 실행때 (코드가 실행되는 시기), typeof foo를 foo = ‘foo’ 할당문보다 먼저 만나게 되죠.

아래의 코드는 인터프리터가 처음 실행 되었을때의 코드와 같습니다.

"use strict";

function foo() {
  var bam = "bam";
  console.log(bam);
}

console.log(typeof foo); // 'function'
foo = "foo";

ES6

ES6는 자바스크립트의 미래이며 대부분의 개발자들이 사용르 하고 있으므로, ES6 예제를 보도록 하겠습니다.

호이스팅은 위에서 보던 var 변수와 다르게 let과 const 변수에서 발생하지 않습니다. 그러나 let과 const 변수는 호이스팅이 되며 차이점은 런타임동안 할당이 끝나기전까지 접근할 수 없습니다.

ES6 문서에 의하면:

변수는 자기자신을 포함하는 lexical 환경이 인스턴스화 될 때 생성되지만 변수의 lexicalBinging 평가 과정이 끝나기 전 까지는 접근할 수 없습니다.

결국에는 인터프리터가 컴파일런 과정중에서 변수들을 호이스팅 하지만 할당이 끝나기 전에 접근하면 에러를 발생시킨다는것이며, 결국에는 변수들의 할당이 끝나기 전까지 접근을 차단한다는 뜻입니다.

Conclusion

이 글을 읽고 자바스크립트에서 호이스팅이 어떻게 발생하는지 이해하셨으면 좋겠습니다. 보기보다 어렵거나 복잡한것은 아니지만 다양한 예시를 분석하고 내부에서 어떻게 작동하는지 방식을 이해하기 위해 다양한 시나리오를 시도해 보는것이 좋습니다.

궁금하신점이 있으시면 주저하지말고 아래 댓글을 달아주세요 - 피드백은 언제나 환영입니다.

원문: Understanding Hoisting in JavaScript