말 그대로 밧줄로 끌어올리는 동사를 의미하는 이 용어는 자바스크립트의 컴파일러가 작동하는 방식 중 하나를 의미한다.
자바스크립트는 기본적으로는 인터프리터 언어이지만, 컴파일 작업이 필요할 때에는 컴파일도 사용하는 JIT(Just-In-Time) 컴파일러이다. 그래서 컴파일러라고 명칭하겠다.
JavaScript에서 호이스팅(hoisting)이란, 인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미합니다. var로 선언한 변수의 경우 호이스팅 시 undefined로 변수를 초기화합니다. 반면 let과 const로 선언한 변수의 경우 호이스팅 시 변수를 초기화하지 않습니다.
출처(https://developer.mozilla.org/ko/docs/Glossary/Hoisting)
즉, 코드 중 선언되어 있는 변수(함수 포함)가 있다면 이들을 위한 공간을 미리 할당해주기 위해 '끌어올려'주는 것이다. 자바스크립트를 크롬 브라우져를 통해 실행시키게 된다면 크롬에 내장된 V8 엔진이 컴퓨터가 이해할 수 있는 LOW-LEVEL의 언어로 변환시켜준다. 이 전에 컴파일러는 JS로 쓰여진 문서를 돌면서 모든 스코프와 컨텍스트에 선언된 식별자들을 미리 검색하게 된다. 이를 통해 우리는 코드를 실행하기 전 모든 위치에 있는 식별자들을 알고 있게 되는 것이다.
사실상 식별자라는 무서운 이름을 썼지만, JS에서는 '변수' 호이스팅과 '함수' 호이스팅 2가지로 나뉘게 된다. 이 중 함수 호이스팅이 변수 호이스팅보다 먼저 일어나게 된다.
var hi = "hi"
var func = func() {}
//func도 사실 변수에 할당되는 거라 구분이 무의미한...
여기서는 func도 변수 호이스팅이다.
호이스팅은 인접 스코프의 상단에서 선언부를 관측할 수 있는 현상이다. 자바스크립트에서 변수를 만들 때에는 선언-초기화-할당 이 세가지 프로세스를 거치게 된다. 즉, 선언이 되는 모든 변수, 함수들을 호이스팅해간다는 뜻이다.
하지만 var과 함수와는 달리, let/const는 호이스팅은 되긴 하지만 사실상 호이스팅이 되지 않는 것과 같다(오해하지 말자. 호이스팅은 된다). let과 const도 선언부까지만 만들면 호이스팅에 포함은 되지만, var은 선언과 거의 동시에 초기화도 같이 일어나는 것에 비해(undefined), let과 const은 호이스팅이 끝나고 변수를 초기화한 후 코드 실행과정에 초기화와 할당이 이루어지기 때문이다.
//1.
console.log(hi)
var hi = 0
console.log(hi)
//2.
console.log(hi)
let hi = 0
console.log(hi)
첫번째 코드의 작동 과정은 다음과 같다
//1.
console.log(hi)
// 이미 var hi = undefined로 선언과 초기화가 동시에 일어난 상황이므로
값은 undefined
var hi = 0
// var hi = undefined로 되어있는 상황에서 hi = 0으로 값을 할당
console.log(hi)
// hi = 0이므로 값은 0이 출력
두번째 코드의 작동 과정은 다음과 같다
//2.
console.log(hi)
// let hi; 선언만 되어 있는 상태로 인식이 됨.
초기화가 되기 전에(값이 undefined라도 할당되기 전) 참조하였으므로
값을 불러올 수 없기에 reference error
let hi = 0
// 1. let hi = undefined
2. hi = 0
초기화와 할당이 코드 실행 순서 때 수행됨.
(할당값이 있다면, 없으면 var의 초기화랑 마찬가지로 undefined가 초기값)
console.log(hi)
// 0이 출력되어야 하나, reference error로 인해 실행되지 않음
이런 이유로 호이스팅이라는 개념은 식별자인 모든 것들에 적용이 되지만, '참조'가 불가능한 것은 let과 const라고 얘기할 수 있겠다.
함수 호이스팅은 개발하는 팀의 개발 스타일에 따라 정하면 된다. 가령 '클린 코드'를 작성하는 메뉴얼 중에는 추상화정도가 높은 정도에서 낮은 정도로 코드가 읽혀야 된다고 한다. 예를 들어서 다음과 같은 코드를 생각해볼 수 있다.
function a() {~~}
function b() {~~}
function c() {~~}
function getUserName() {
a();
b();
c();
}
일반적인 코드 기법이라고 한다면, 다음과 같이 함수 a,b,c에 대한 선언과 할당을 해준 후, getUserName 함수를 호출하는 것이 논리적일 것이다. (호이스팅을 빼고 생각했을 때)
하지만 함수 호이스팅을 이해하며, 코드의 추상화정도를 따진다면 다음과 같이 작성도 가능하다.
function getUserName() {
a();
b();
c();
}
function a() {~~}
function b() {~~}
function c() {~~}
이건 사람마다 개인차가 있을 수도 있다. 먼저 추상화 정도가 높은 함수를 미리 보면서, 내려가면서 코드 추상화정도가 낮은 코드를 보면 이해도가 높아질 수 있다.
하지만 당연히, 함수를 변수에 할당하면 똑같이 변수 호이스팅에 해당된다!! 그래서 다음과 같은 상황은 적용되지 않는다.
function getUserName() {
a();
b();
c();
}
let a = function () {~~}
let b = function () {~~}
let c = function () {~~}
위와 비슷한 맥락으로 변수 호이스팅은 엄격히 제한된다. var은 함수 스코프이므로 함수가 아닌 곳에서 선언이 될 경우 커다란 문제점을 낳을 수 있다.
for(var i = 0; i < 5 ; i++) { console.log(i )}
console.log(i)
// 함수를 통한 새로운 스코프가 만들어져있지 않으므로,
var은 스코프 최상단인 사실상 body 태그의 전역변수로 만들어진다.
출력값은 1, 2, 3, 4, 5
그래서 var만 사실상 호이스팅을 써먹을 수 있는 능력을 갖추었지만 var의 이용보다는 let과 const를 이용하여 side effect를 없애는 추세이기 때문에 사실상 변수 호이스팅을 이용한 코드 추상화 작업은 없다고 봐도 무방하겠다.
참고 사이트
1. https://tecoble.techcourse.co.kr/post/2021-04-25-hoisting/ (호이스팅)
2. https://velog.io/@gusdnr814/Javascript%EB%8A%94-%EC%9D%B8%ED%84%B0%ED%94%84%EB%A6%AC%ED%84%B0-%EC%96%B8%EC%96%B4%EC%9D%B8%EA%B0%80 (인터프리터, 컴파일러)
3. https://onestone-dev.tistory.com/3 (코드 추상화)