프로그래밍 언어/Javascript

[JavaScript] Hoisting이란?

hanrabong 2022. 4. 19. 19:32

 

JS(ko.wikipedia.org)

Hoisting이란?

 변수나 함수 선언문 등을 해당 scope의 선두로 옮긴 것처럼 동작하는 특성을 말합니다. 코드를 통해서 알아보겠습니다.

console.log(a);
var a = 'Hello World';

 다음과 같은 코드를 실행하면 javascript는 코드를 순차적으로 실행을 하기에 a 값이 없다고 error가 나와야합니다. 하지만 위의 코드를 실행하면 'undefined'라는 결과가 나옵니다. 그 결과가 나오는 이유는 위의 코드가 아래와 같이 동작을 하기 때문입니다.

var a;
console.log(a);
a = 'Hello World';

 첫 번째 코드를 실행시키면 Hoisting에 의해 두 번째 코드처럼 실행이 된다고 하였습니다. 그렇다고 해서 실제로 물리적으로 바뀌는 것이 아니라 선두로 옮긴 것처럼 동작하는 특성을 말하는 것입니다.

 

 

그럼 hoisting은 언제 그리고 어떤 원리에 의해서 동작하게 되는 것일까요?

 

 

Hoisting이 어떻게 동작을 하는 걸까?

 compile 시기에 code가 작동하기 전에 함수 및 변수 선언을 검색합니다. 검색한 모든 함수 및 변수 선언은 Lexical Environment라는 Javascript data structure 내부의 메모리에 추가됩니다. 이로 인해 source code에서 실제로 선언되기도 전에 사용할 수 있게 됩니다.

 

Lexical Environment란?

 위에서 말했듯이 Lexical environment는 Javascript data structure로 identifier-variable mapping을 뵤유하고 있습니다. (여기서 identifier는 변수 또는 함수의 이름을 말하며 variable은 실제 객체 또는 primitive value에 대한 참조입니다.)

구조를 개념적으로 보면 다음과 같습니다.

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

간략히 말해 Lexical environment는 프로그램 실행 중에 변수와 함수가 존재하는 장소입니다.

 

Lexical environment에 대해 자세히 알고 싶으면 다음 글을 참고해주세요!!

2022.03.20 - [웹/Javascript] - [Javascript] Javascript 동작(1)

 

[Javascript] Javascript 동작(1)

Javascript란? Javascript는 싱글 쓰레드로 동작하는 객체 기반의 스크립트 언어입니다. 웹 브라우저나 Node와 같은 런타임 환경에서 실행이 됩니다. Javascript에는 hoisting, call by sharing, prototype, clos..

hanrabong.com

 

 

 

함수 선언과 함수 표현식 Hoisting

hello();

function hello(){
	console.log('hello');
}

 위와 같은 코드가 있다고 해봅시다. 앞서 배운 compile time에 hello라는 function을 lexical environment에 다음과 같이 저장할 수 있습니다.

LexicalEnvironment = {
  hello: < func >
}

 이처럼 저장이 되기에 Javascript engine이 hello() 코드를 마주쳤을 때 lexical environment를 확인하고 hello 함수를 실행시킵니다.

 

 

이번엔 다음과 같이 코드를 짜보겠습니다.

hello();

var hello = function(){
  console.log('hello!');
}

위의 코드를 실행시키면 'TypeError: hello is not a function' 이라는 error 문구가 나옵니다. 왜 error문구가 나오는 걸까요? 그 이유는 hoisting은 선언만 할 뿐이지 값을 할당해주지 않습니다. 제일 처음 코드를 보면 console.log(a)의 결과가 undefined 인것을 보면 알 수 있습니다. 값을 넣어주지는 않고 선언만 해줍니다.

 따라서 위와 같은 함수 표현식은 hoisting되지 않습니다. 좀 더 정확히 말하면 함수로써 hoisting이 되지 않는 것이고 hello를 variable로 취급하여 hoisting을 하게 되어 TypeError가 나오는 것입니다.

 

 

let을 hoisting했을 때 error가 나오는 이유가 무엇일까?

먼저 var을 이용한 hoisting을 보겠습니다. 

console.log(a);
var a = 'Hello World';

 위의 코드는 제일 처음에 hoisting을 설명하면서 예시로 보여준 코드입니다. 위 코드를 실행하면 output은 undefined 가 나옵니다. 왜 undefined가 나오게 되냐면 hoisting은 선언만 해주는 것이지 값을 할당하지는 않습니다. 따라서 compile 단계에서 값을 Lexical environment에 넣고 처음에 undefined로 초기화를 해줍니다. 그 후에 값을 할당하는 code를 마주쳤을 때 해당 값을 할당해줍니다.

 

 

두 번째로 let을 이용한 hoisting을 보겠습니다.

console.log(a);
let a = 'Hello World';

 위의 코드를 실행시키면 'ReferenceError: Cannot access 'b' before initialization' 라는 error가 발생합니다. 위의 코드와 let, var만 빼고 똑같은 코드인데 오류가 나왔습니다.

왜 이런 결과가 나왔을까요?

 간략히 말하자면 Javascript에서 함수 및 변수 선언이 hoisting될 때 var을 이용한 선언은 undefined로 초기화가 되고 let, const의 경우 uninitialized로 초기화가 되기 때문에 발생합니다.

let과 const의 경우 Javascript engine이 runtime에서 선언문을 만날 때만 초기화됩니다. 이로 인해 source code에서 변수의 선언된 위치를 만나기 전에는 변수에 접근할 수 없습니다. 이것을 'Temporal Dead Zone' 이라고 부릅니다. 변수 선언과 접근할 수 없는 초기화 사이의 시간 범위라고도 하며 변수 선언은 hoisting에 의해 선언되기에 block local scope에서 선언되었을 때까지 시간이라고 합니다. Javascript engine이 변수가 선언된 줄에서 여전히 값을 찾을 수 없으면 undefined를 할당하거나 const의 경우 오류를 반환합니다.

{
// TDZ 시작
// TDZ
// TDZ
// TDZ
// TDZ
	let a;   // TDZ 끝
	console.log(a);   // TDZ 존재 X
	a = 'Hello World';   // TDZ 존재 X
}

 위와 같은 코드의 경우에 처음 hoisting에 의해 a가 LexicalEnvironment에 다음과 같이 저장이 됩니다.

LexicalEnvironment = {
  a: <uninitialized>
}

 이 후에 let a;라는 code를 마주치면 다음과 같이 변합니다.

LexicalEnvironment = {
  a: undefined;
}

그 후에 a = 'Hello World'라는 code를 만나면 a에 'Hello World'라는 값이 할당됩니다.

 

 

Class 선언과 Class 표현식 Hoisting

 let 과 const와 마찬가지로 Class 또한 Temporal Dead Zone이 존재합니다. Class또한 선언(hoisting)과 초기화가 따로 이루어지기 때문에 source code에서 선언하는 줄을 마주쳐야 초기화가 됩니다.

let peter = new Person('Peter', 25); 

console.log(peter);
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

 위와 같이 코드를 실행하면 'ReferenceError' 가 발생합니다. 그러하여 먼저 선언을 해주고 접근을 하여야 합니다.

 

 

Class 표현식은 함수 표현식과 마찬가지로 변수 취급을 하기에 의도하는 바인 Class로 hoisting이 되지 않습니다. 따라서 미리 선언을 하고 참조해서 사용하여야 합니다.

let Person = class {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}
let peter = new Person('Peter', 25); 
console.log(peter);
// Person { name: 'Peter', age: 25 }

 

 

※ 궁금한 점이나 틀린 점이 있으면 댓글 달아주세요

 

 

참고자료

https://blog.bitsrc.io/hoisting-in-modern-javascript-let-const-and-var-b290405adfda

 

Hoisting in Modern JavaScript — let, const, and var

How Hoisting Really Works in JavaScript

blog.bitsrc.io

 

'프로그래밍 언어 > Javascript' 카테고리의 다른 글

[Javascript] Data type - Primitive type  (0) 2022.04.26
[Javascript] Closure란?  (0) 2022.04.23
[Javascript] Javscript 동작(2)  (0) 2022.04.05
[Javascript] Javascript 동작(1)  (0) 2022.03.20