1. 들어가기에 앞서
1.1. 호이스팅의 정의
호이스팅은 끌어올리다 라는 의미를 가진 Hoist 에 ing를 붙인 단어이다. 이러한 어원을 바탕으로 자바스크립트에서는 호이스팅이라는 용어를 “변수 또는 함수의 선언부가 위치한 인접 스코프의 시작 지점에서 해당 식별자의 관측이 가능한 현상” 이라고 말할 수 있다. 다소 정의가 어렵고 난해하게 들린다. 이를 조금 더 쉽게 풀어서 얘기하자면, 특정 범위(스코프) 내에서 선언된 변수나 함수가 작성된 코드의 위치와 상관없이 해당 범위(스코프)의 최상단에 선언한 것 처럼 끌어올려주는 현상이라고 말할 수 있다. 하지만 그렇다고 해서 실제로 JS 엔진이 특정 변수나 함수를 최상단으로 끌어올리는 것은 아니다. 마치 그렇게 보일 뿐이다.
1.2. 호이스팅이 발생하는 이유
그렇다면 JS에서는 호이스팅이 왜 발생한걸까? JS 코드는 JS 엔진을 통해 기계어로 변환하는 과정을 거치는데, 그 과정을 간단히 설명하자면 다음과 같다.
•
JS Code → Token (by Tokenizer) → AST (by Parser) → Byte Code
위 과정에서 JS 엔진은 모든 범위(스코프)를 탐색하며 유효 범위(스코프) 내의 변수 및 함수 정보를 수집한다. 따라서 특정 함수가 실행될 때, 선언된 변수 및 함수 정보를 알고 있기 때문에 스코프의 어느 지점이든 관련 변수 및 함수를 참조할 수 있게 된다. 이러한 이유로 호이스팅이 일어나는 것이다.
실행 컨텍스트의 Lexical Environment 는 Environment Record (환경 레코드)와 Outer LexicalEnvironment Reference (외부 렉시컬 환경 참조 컴포넌트)로 구성된다.
Environment Record 는 유효 범위 안의 식별자와 결과값을 바인드 해서 기록하는 영역이다. 여기에 함수 선언, 변수명 등이 담기는데, 컨텍스트 내부 전체를 처음부터 끝까지 훑으면서 순서대로 수집한다. 따라서 자바스크립트 엔진은 코드가 실행되기 전에도 이미 해당 환경에 속한 코드 변수명들을 알고 있기 때문에 호이스팅이 일어나는 것이다.
1.3. 호이스팅 범위/대상
자바스크립트의 모든 변수와 함수는 호이스팅이 가능하다. 다만 어떻게 해석하냐에 따라서 let, const 변수와 함수 표현식은 호이스팅이 안된다고 볼 수도 있다. 그러나 필자는 호이스팅 (유효 범위 최상단에 선언 되는 것) 자체는 가능하다고 결론을 내렸다. 변수와 함수의 타입에 따라 호이스팅 규칙이 다소 다를 뿐이다. 이를 조금더 상세히 살펴보도록 하자.
2. 변수의 호이스팅
2.1. 변수의 생성 과정 (variable lifecycle)
자바스크립트에서 변수의 생성단계는 크게 아래와 같이 3가지로 나뉜다.
1.
Declaration phase (선언)
•
파싱 과정에서 변수 객체가 변수에 대한 식별자들을 수집한다.
•
변수 객체는 스코프가 참조하는 대상이 된다.
2.
Initialization phase (초기화)
•
선언 단계에서 생성된 변수를 위해 메모리를 할당하는 단계이다.
•
할당된 메모리에 undefined를 할당한다.
3.
Assignment phase (할당)
•
초기화 단계에서 undefined 로 할당된 변수에 다른 값을 할당한다.
2.2. var, let, const의 변수 생성 과정
var 는 선언과 초기화가 거의 동시에 진행된다. 실행 시점에 스코프 최상단에서 해당 변수에 대한 메모리가 undefined 인 상태로 살아 있기 때문에, 선언문의 위치와 관계없이 참조(호출) 및 할당이 가능하며 Reference 에러가 발생하지 않는다.
그러나, let 과 const 는 다르다. 선언과 초기화가 각각 분리되어 진행되어 진행된다. 따라서 실행 시점에 실질적인 초기화/할당을 만날때까지 변수에 대한 메모리는 존재하지 않기 때문에 선언부 상단에서 참조(호출) 및 할당이 불가능하다.
즉, 호이스팅이 되지 않는 것이 아니라, 호이스팅은 되었으나 초기화(메모리 할당)가 되지 않았기 때문에 변수에 접근을 할 수 없는 것이다. 이와 같이 스코프의 진입(시작) 시점과 초기화 시작 시점의 사이를 일시적 사각지대, TDZ(Temporal Dead Zone) 이라고 한다. 즉, 변수는 존재하나 초기화가 되어 있지 않은 구간이라고 말할 수 있다.
// Uncaught ReferenceError: Cannot access 'tdz' before initialization
// 호이스팅은 되었으나, 초기화 및 할당 되지 않아 참조가 불가능하다. TDZ의 영역이다.
console.log(tdz);
let tdz = "now assigned";
console.log(tdz); // now assigned
JavaScript
복사
3. 함수의 호이스팅
함수의 호이스팅은 함수 표현식인지 함수 선언문인지에 따라 다르게 동작한다. 이게 다소 이상하게 들릴수도 있을 것이다. 그러나 변수의 호이스팅과 크게 다르지 않다.
3.1. 함수 표현식
함수 표현식은 변수에 함수를 할당하는 방식으로 작성된다. 따라서 우리는 함수표현식은 변수의 호이스팅 규칙이 적용된다고 말할 수 있다. 아래의 예시를 살펴보자.
myFunction 은 var 변수이다. 앞서 살펴본 바와 같이 var 변수는 선언과 초기화가 동시에 이루어지기 때문에 선언부의 위치와 관계없이 호출이 가능하다. 그러나 myFunction() 은 실제 var 변수의 할당 단계에서 할당된 값을 호출하는 것을 의미한다. 실행시점의 스코프 최상단에서 var 변수는 호이스팅에서 선언과 초기화까지만 이루어진 상태이기때문에 당연히 myFunction() 를 참조할 경우 에러가 발생한다.
// undefined
console.log(myFunction);
// Uncaught TypeError: myFunction is not a function
console.log(myFunction());
var myFunction = function() {
console.log("This is a function expression.");
};
JavaScript
복사
반대로 myFunction 이 const 변수인 경우, const 변수의 생성 단계에 따라 선언과 초기화가 분리되어 있기 때문에 실행 시점의 스코프 최상단에서 해당 변수를 호출할 경우 TDZ상태인 myFunction 에서는 에러가 발생한다.
// Uncaught ReferenceError: Cannot access 'myFunction' before initialization
console.log(myFunction);
const myFunction = function() {
console.log("This is a function expression.");
};
JavaScript
복사
3.2. 함수 선언문
함수 선언문은 선언, 초기화, 할당 단계가 동시에 진행된다. 따라서 선언문의 위치와 관계없이 함수를 호출해도 문제없이 할당된 함수 내용이 반환된다.
console.log(myFunction); // Output: [Function: myFunction]
function myFunction() {
console.log("This is a function declaration.");
}
JavaScript
복사
•
함수 선언문의 생성 단계
4. 정리
•
호이스팅 정의
◦
스코프 내 선언된 위치과 관계 없이 해당 스코프의 최상단에 선언된것처럼 (마치 끌어올려진것 처럼) 보이는 자바스크립트 내 고유 현상
•
호이스팅이 발생하는 이유
◦
자바스크립트가 기계어로 변환되는 과정에서 자바스크립트 엔진이 각 스코프 내 변수 및 함수들의 정보를 수집하고 있기 때문에 어느시점에서든 함수 및 변수를 참조할 수 있게 되는 것이다
•
호이스팅의 규칙
◦
모든 변수와 함수는 호이스팅(유효 스코프 내 최상단에 선언되는 것)이 가능하다.
◦
다만 타입에 따라 호이스팅 규칙이 다르다.
•
변수의 호이스팅 규칙
◦
var 상단에서 참조 및 할당이 가능하다.
◦
let , const 는 상단에서 참조 및 할당이 불가능하다 (선언 단계만 진행되어있기 때문. TDZ 이슈)
•
함수의 호이스팅 규칙
◦
함수 표현식은 변수의 호이스팅 규칙을 따른다.
◦
함수 선언문은 상단에서 참조 및 할당이 가능하다.