JS - 2 hours daily: Hoisting

박상하·2023년 8월 18일
0

TIL  CS/JS

목록 보기
10/22


오늘은 호이스팅을 다뤄보겠다! Hoisting은 사실 이전의 포스팅 한 주제들 보다 이해도가 더 있다.
그렇지만 오늘도 다루는 이유는 확실히 집고 넘어가기 위해서이다! 호이스팅을 면접관님 앞에서 설명하라고 한다면 정말 막힘없이 술술 말할 수 있을까?

그렇지 않다는 건 아직 정확하기 정리가 안되었다는 것이라 생각한다! 이번 기회에 정확하게 학습하고 넘어가자!

Hoisting(호이스팅) 이란 ❓

Hoisting => 끌어올린다.

결론부터 말하자면 변수 및 함수가 스코프 내의 최상단으로 끌어올려지는 현상을 말한다.

그럼 장말 변수나 함수가 맨 위로 올라오는 것일까?? 정확히는 그렇지 않다. 맨 위로 올라오는 것이 아닌
가장 먼저 저장이 된다고 볼 수 있다. 자, 그럼 호이스팅에 대해 더 자세히 알아보자.

먼저 호이스팅을 이해하기 위해서는 실행컨텍스트를 이해해야한다.

실행컨텍스트에 대한 아주 좋은 유튜브 동영상이 있다.
레전드 실행컨텍스트 설명 영상

위 영상과 학습된 내용을 기반으로 설명을 해보겠다.

브라우저 마다 다른 렌더링 엔진을 갖는다고 저번 포스팅에서 설명했다.

렌더링 엔진: Chrome=> blink , Firefox=> gecko, Safari=> webkit

그런데 이제 브라우저 마다 각각 다른 자바스크립트엔진(자바스크립트 해석기)를 갖는다.

Browser 마다 다른 JS engine ❗️

JS engine: Chrome=> V8, FireFox=> SpiderMonkey, Safari=> Javascript Core

그렇다. 각각 Browser마다 다른 JS engine을 가지고 이 엔진은 JS code를 읽을 수 있다.

그럼 어떻게 엔진은 JS code를 읽어올까?

JS engine은 JS code를 읽고 실행컨텍스트를 낳는다 ❓

JS engine은 먼저 JS code를 읽어온다. 그럼 읽은 후에 실행 컨텍스트라는 것을 형성하고 이곳에 먼저
변수와 함수를 담아 놓는다.

이게 핵심이다! JS 엔진은 JS 코드를 읽으면서 실행컨텍스트를 형성하고 이곳에 변수와 함수를 담아서 기억한다!

이는 스코프 단위로 형성이 되는데 설명하자면 다음과 같다.

먼저 JS engine은 전역적으로 코드를 한번 쑥 훑는다. 이때 변수와 함수를 기억하기 위해 실행컨텍스트에 담아둔다. 이를 전역실행컨텍스트 라고한다.

그 다음 함수의 스코프 단위로 또 실행컨텍스트가 형성된다. 즉, 함수가 실행되면 해당 스코프 내부의 실행 스코프가 생성이 된다. 그리고 또 그 안에서 함수가 실행된다면 해당 스코프는 또 실행컨텍스트가 생성이되고 함수가 종료되면 해당 실행컨텍스트는 사라진다.

그럼 가장 마지막에 남는 컨텍스트는?? 전역실행컨텍스트로 전역에 담긴 변수와 함수에 대한 기억을 가장 오래 가지고 있다.

그림으로 한번 그려보면 다음과 같다.

JS engine이 JS code를 읽으면서 실행컨텍스트가 형성되어 변수와 함수가 저장되면서 발생하는 현상이다

그럼 이제 코드로 한번 살펴보자!

코드로 살펴보는 호이스팅 ❗️

let A = "123"

const AFn=()=>{
console.log(A)  

}
AFn()

다음 코드는 어떤 결과가 나올까??

자, 전역 실행컨텍스트에 A와 AFn이 담긴다 이때, let과 const로 선언했기 때문에 초기화는 이루어지지 않은 상태다. 이는 저번포스팅에서 설명을 했었는데

let과 const는 메모리에 등록은 되지만 값의 초기화는 이루어 지지 않는다!

즉 , let 과 const를 위한 방은 빼놨지만 어떤 방인지는 아직 알려주지 않은 상태이다! 이 상태에서 let과 const로 선언한 변수를 찾고자 하면 방을 찾을 수 없다고 나오고 참조에러가 발생한다.
이렇게 참조에러가 발생하는 구간을 TDZ(Temporal Dead Zone)이라고 한다.

대신 var로 선언을 하면 초기화가 메모리 할당과 동시에 발생하기 때문에 참조에러가 발생할 일은 없다.

함수도 마찬가지이다. 표현식인 const,let,var는 일반 변수와 똑같이 메모리할당과 초기화가 이루어지지만
선언식은 함수의 정체를 모두 다 저장하기 때문에 선언식으로 함수를 선언하면 일단 선언만 한다면 어디서든 함수를 사용할 수 있다.

다시 코드로 돌아와서!!

정답은 "123" 출력이다.

전역실행컨텍스트에 let A의 메모리할당과 AFn의 메모리할당이 이루어지고 위에서부터 let A = "123"이라는 값을 배정받는다 그리고 Afn이 선언될때!! A => 그 상위 스코프인 전역스코프의 A를 찾게된다

여기서 중요한 개념 2가지가 나왔다.

스코프체인 ❓

스코프 체인이란?? 실행컨텍스트끼리 참고하는것이다.

이게 무슨말이냐면?! 결국 처음에 실행컨텍스트가 형성될 때 변수와 함수에 대한 초기 정보를 저장한다 이를 생성단계라고한다. 생성단계 이후 코드를 직접 스캔하면서 실행단계가 진행되는데 이때 값의 update가 일어난다.

즉 전역스코프 다음 A scope가 있다고 할때 A scope는 전역스코프를 참조할 수 있다는 점이다.

왜냐면? 전역스코프가 생성될때 A scope가 특정변수보다 더 밑에 있다면 특정변수는 이미 값의 업데이트를 마친 상태이고 그렇다면 A scope는 특정 값을 정확하게 사용할 수 있다.

let A = "123"

const AFn=()=>{
console.log(A)  

}
AFn()

다시 이 코드를 가져와보면 Afn내부의 A는 결국 외부 스코프인 전역스코프에서 let A를 가져온다.

이를 스코프 체인이라고한다. 이해가 더 쉽게 되도록 코드를 하나 더 가져와보겠다.

let A="123"

const B=()=>{
  const C=()=>{
    console.log(A)
  }
  C()
}
B()

다음과 같은 함수가 있다면 어떻게 될까? B함수는 C를 실행하는 함수이고 C는 변수 A를 출력하는 함수이다.

순서는 다음과 같다.

  1. 전역 실행 컨텍스트 형성
    A에 대한 메모리 확보, B함수에 대한 메모리확보
  2. let A를 읽고 값 update => let A = "123"
  3. const B 읽고 값 update
  4. const B 내부 scope 실행컨텍스트 형성 C에 대한 메모리확보
  5. const C 읽고 값 update
  6. C 실행
  7. C 내부에서 console.log(A)실행
  8. 이떄 A의 값을 찾기 시작함!
    이게바로 scope chaining(스코프 체이닝)
  9. A출력 (C 실행컨텍스트 삭제)
  10. B실행컨텍스트 삭제
  11. 전역 실행컨텍스트 삭제

렉시컬 환경 ❓

var A = "123"
function AFn(){
console.log(A)

}
function BFn(){
 const A ="456"
 Afn()
}
BFn()

다음 함수는 어떤 결과값을 뿌리게될까??

자 일단 AFn은 변수 A를 출력하는 함수이다.

그럼 AFn은 전역스코프의 var A를 참조할까 아니면 BFn의 const A를 참조할까??

정답은 !! var A="123"을 출력한다.

왜냐하면 함수 스코프 내부에서 변수를 바인딩할 때는 이미 선언할 때 모두 정해진다. 즉 해당 스코프 내부에서 실행컨텍스트가 형성되면서 초기에는 값에 대한 메모리저장 초기화가 일어나고 이 후 코드를 읽으면서 실행단계에서 A에 대한 값을 업데이트하는데 이게 선언할 때 해당 스코프를 기준으로 발생하기 때문이다.

렉시컬 환경: 스코프 내부에서 변수가 어떤 값으로 바인딩되는지에 대한 개념이고 이때, 결국 함수는 어디서 선언되었는지에 따라 변수 바인딩에 영향을 준다는 개념!

0개의 댓글