개요
사실 호이스팅의 개념은 정리한지 꽤 지났다.
하지만 이전에 한번 정리해본 내용을 토대로 남에게 설명해보니 질문을 받고 혼자 정리하면서 왜 이렇게 동작하는가 생각되는 것들이 많아서 조금 더 상세하게 적어서 작성해서 조금 더 좋은 자료로 발표하기 위해 딥하게 들어가보았다
개념을 크게 몰라도 상관없지만 그래도 조금 더 확장해서 다루기 위함이니 한 번쯤은 다시 보고 오면 이해가 쉬울거라 생각된다.
그럼 어떤 부분이 많이 헷갈렸을까?
당연히 변수를 끌어 올리는건 아니다
대표적으로 많이 사용되는 문장이 var, let, const 변수 선언 방식에 대해 "변수를 마치 끌어올린 것처럼 보인다" 하는 문장인데 물리적으로나 논리적으로 코드가 끌려 올라가지 않기 때문에 실질적으로 잘 와닿지 않는다
그럼 실제로 어떻게 진행되는걸까?
이건 Two pass 컴파일 개념을 이해하면 쉽다
Two pass란 코드를 해석하고 실행하는 과정이 Two pass, 즉 두 단계로 이루어진다는 것이다.
첫 번째 과정은 변수만 var, let, const, 함수 등 변수만 읽어 변수 테이블에 저장한다
두 번째 과정은 자바스크립트 엔진이 변수테이블을 참조하여 최상단부터 코드를 하나씩 실행하는 과정으로 이루어진다.
이때 var, let, const의 차이는 첫 번째 패스에서 발생하는데 변수를 읽어 저장하는 방식에 차이가 난다.
위 포스트를 읽어보고 실제 변수가 만들어지는 과정을 정리해보면 다음과 같다
첫 번째 패스에서 var 선언을 만났다면 자바스크립트 엔진은 AllocateTo 메소드를 통해 변수명을 테이블에 저장한 뒤 실제 메모리 공간을 만들어낸다. 다시 계속 진행하다가 첫 번째 패스 중 var이 아닌 let과 const 선언을 만나면 set_initializer_position 메소드를 통해 변수 테이블에만 저장한다. 이때 실제 메모리 공간은 만들지 않는다.
이렇게 모든 변수를 읽어 변수 테이블을 완성하고 첫 번째 패스가 끝난다면 이렇게 만들어진 변수 테이블을 참조하여 실제 코드를 해석과 실행하게 되는데 여기서 TDZ 또한 설명된다. 변수는 이미 변수 테이블에 등록되어 있지만 let과 const는 메모리 공간이 아무 것도 만들어지지 않고 position이라는 메소드명처럼 실제 코드를 두 번째 패스에서 만나기 전까지의 변수명만 있는 순간을 TDZ로 설명할 수 있다.
이제 두 번째 패스가 시작되면서 인터프리터가 코드를 하나하나 해석하고 실행하면서 다시 그 변수를 만날 때 메모리 공간 초기화, 실제 값 할당을 시작한다. 이때 var은 값 할당만 진행한다.
사실 투패스로 진행한다는 것과 실제 메소드 선언이 다르다는 것만 알면 호이스팅에 관해 어느정도 이해할 수 있다고 생각한다. 변수 할당 메소드명 자체에도 position이 들어가면서 어떻게 동작할 것이라고 유추하기 쉽다. 또한 호이스팅 뿐만 아니라 패스1 당시 변수 선언으로만 만들어지는 변수 테이블은 바로 스코프, 스코프체인의 개념과 이어져있다.
호이스팅 요약
1. JS는 투패스 인터프리터이다.
2. 첫 번째 패스에는 변수 테이블(스코프)를 만드는데 이때 변수만 읽는다
3. 변수를 테이블에 저장하면서 var은 allcateTo를 통해 메모리 공간을 만들면서 저장
4. let과 const는 변수명만 테이블에 올리고 메모리 공간은 만들지 않는다.
5. 두 번째 패스에서는 코드를 한줄한줄 읽으며 실행한다
6. var는 값을 할당만 하고 let과 const는 메모리 공간 만들기 + 값 할당을 동시에 한다
7. TDZ는 변수명만 테이블에 올라가 있고 두 번째 패스에서 변수를 만나기 전까지 이다
조금 더 생각해보기
하지만 왜 굳이 변수 할당 방식을 다르게 설정해서 호이스팅이 발생하는 것인지는 잘 이해가 되지 않았다.
찾아보니 전향참조의 특성을 활용하기 위함이라고 정리되어 있어 마지막으로 더 찾아보게 되었다
전향참조(forward reference)
전향참조란 변수를 아직 선언하지 않았는데 변수를 사용할 수 있게 해준다. 이렇게 변수를 사용하게 된다면 순환 구조를 만들어낼 수 있다.
여기 stack overflow에서 찾아낸 예시가 있다.
함수 선언식도 대표적인 호이스팅의 예시 중 하나인데 아래 코드를 살펴보면 even 함수에서 아직 할당되지않은 odd라는 함수를 실행시킨다. 물론 이런 구조를 실제로 본적은 없고 순환 구조 자체가 구조적으로 오류를 많이 불러 일으킬것이라는 느낌때문에 별로 추천되지 않는 방식이지만 이런 구조가 필요하다면 사용될 수 있다고는 생각된다.
function even(n) { return n == 0 || !odd(n-1); }
function odd(n) { return !even(n-1); }
이 외에도 이식성(상태관리 라이브러리) 등 여러 장점이 있었지만 너무 깊게 들어가는 것 같아서 내용을 추가하진 않았다. 시스템 프로그래밍까지 들어가면 조금 더 깊게 이해할 수 있을거라고 생각한다.
그런데??
추가적으로 더 찾아본 결과
스택오버플로우에서 결국 JS개발자가 직접 의도하지 않은 결과라고 말을 하면서 결국 왜 호이스팅이라는 것을 발생시켰는지는 잘 이해할 수 없게되었다.
하지만 es6 문법에 따라 let과 const가 변수를 할당전에 사용할 수 없게 만들면서 결국엔 호이스팅을 발생시키지 않는 것이 옳다고 생각하고 이 외에도 여러 lint, clean code 면에서도 변수 할당은 최상단에서 이루어질 수 있도록 유도를 하니 여기서 더 깊게 들어갈 필요는 없다고 생각하며 호이스팅을 정리하였다.
그리고 정말 중요한게 투 패스(이중 패스) 방식은 브라우저마다 해석하는 방식이 다를 수 있다고 하니 주의해야할거 같다.
마지막으로 이렇게 정리해보니 스코프가 왜 실행과 관계 없는지 var, let, const가 실제로 어떻게 차이가 있는지 더 확실하게 이해할 수 있었다
'TIL > 트러블슈팅' 카테고리의 다른 글
typescript 제네릭으로 리팩토링 & 변수,타입 수정 (0) | 2023.01.11 |
---|---|
크롬 익스텐션 보일러 플레이트 및 웹팩 설정과 css 에러 수정 (0) | 2023.01.09 |
[Nest] 53431 ERROR [ExceptionsHandler] () is not a function (0) | 2022.11.16 |
Suspense를 잘못 사용하여 배운 페이지가 깜빡이는 문제 (0) | 2022.10.29 |
Next.js static 페이지 업데이트가 되지않았던 이유와 해결 경험 (0) | 2022.10.26 |