JavaScript 런타임 환경

Seung·2022년 2월 28일
0

😄 JavaScript 런타임 환경

  • Microtask Queue
    : Promise then, mutation observer에 등록된 콜백이 들어오는 큐 (이하 MQ)
    : 중간에 렌더링이나 다른 JavaScript 코드가 수행되지 않음을 보장

  • Task Queue (Callback Queue)
    : 특정 이벤트 발생시 지정한 콜백이 들어오는 큐 (이하 TQ)
    : Event Loop가 Call Stack이 비워져있을 때 TQ에 있는 콜백을 한개만 Call Stack으로 가져와 작업을 수행
    : TQ는 heap 영역에 할당

  • Render : Render Tree, Layout, Paint, Request Animation Frame (RAF Queue)

    🥰Render Tree, Layout, Paint에 대한 자세한 설명은 제 벨로그에 있습니다🥰

Event Loop

  • 프로세스가 동작하는 동안 MQ, TQ, Render를 순회하면서 Call Stack이 비어있는지 항상 확인하고 Call Stack이 비워져 있으면 각 Queue에 있던 콜백을 Call Stack으로 옮겨와서 작업을 수행한다 (Call Stack이 비워져있지 않다면 비워질때까지 계속 순회한다)

  • 따라서 Call Stack에서 시간이 오래걸리는 작업을 하게 된다면 Event Loop는 계속 순회를 하기 때문에 실행되지 않는다 (이 점 유의해서 코드를 작성해야 한다)

  • 위와 같은 이유로 Call Stack에서 수행중인 작업은 작업이 끝날 때까지 보장된다 (Call Stack이 비워져야 Event Loop가 각 Queue에 있는 콜백들을 데려오므로)

  • Event Loop는 JavaScript 엔진에 속한다
    🥰JavaScript 엔진에 대한 자세한 설명은 제 벨로그에 있습니다🥰

queue ?

  • FIFO (First In First Out)

  • Stack (Last In First Out)과 반대로 제일 처음 들어온 것이 제일 먼저 나가는 자료구조

  • 대표적인 api : add()와 remove()

런타임 동작 과정
1. Event Loop는 MQ, TQ, Render를 엄청 빨리 순회하면서 일정 시간이 지나면 (보통 16.7ms) 제일 먼저 Render로 입장

2. 브라우저 업데이트 하기 전 RAF Queue에 등록된 모든 콜백을 처리한다 (RAF API를 호출하지 않은 경우 생략)

3. 이후 Render에서 Render Tree -> Layout -> Paint를 통해 브라우저를 업데이트 한다

4. 이후 Event Loop는 Render에서 나와서 MQ로 들어간다

5. MQ에 콜백이 존재하면 콜백을 Call Stack으로 가져와 작업을 수행한다
5-1. 이 때 MQ에 존재하는 모든 콜백의 작업이 끝날 때까지 Event Loop는 순회를 멈추고 대기한다
5-2. 또한 Event Loop는 MQ에 존재하는 콜백의 작업 도중에 MQ에 새로운 콜백이 들어온 것도 Call Stack으로 가져와 작업한다 (즉 Event Loop는 MQ가 텅텅 비어질 때까지 계속 대기한다)

6. MQ의 모든 콜백 작업이 끝나서 MQ가 텅텅 비어지게되면 Event Loop는 다시 순회하다가 TQ에 들어간다

7. TQ에서 콜백이 존재하면 하나의 콜백만 Call Stack으로 가져와 작업을 수행한다 (작업이 끝날 때까지 Event Loop는 대기

8. TQ에서 하나의 콜백의 작업이 끝나면 다시 순회를 시작한다 (1번으로 되돌아감)

MQ와 TQ의 가장 큰 차이점
Event Loop가 MQ를 한번 방문하면 Queue가 완전히 비어질때까지 작업을 수행 (작업이 끝날 때까지 Event Loop는 대기)
Event Loop가 TQ를 방문하면 Queue에 콜백이 여러개 있어도 하나의 콜백만 꺼내서 Call Stack으로 가져와 작업을 수행하고 다시 순회 (즉 TQ에 남은 콜백이 있어도 하나의 콜백 작업만 수행하고 다시 순회함)

16.7ms?

  • 브라우저의 업데이트 내용을 사용자 눈에 부드럽게 보이게 할려면 통상 60fps (1초동안 60개의 프레임)이 적절하다고 한다

  • 60fps로 하기 위해서는 보통 16.7ms동안 업데이트가 일어나야 한다. 16.7ms를 초과하게 된다면 브라우저 업데이트 내용이 사용자 눈에 렉 걸린 것처럼 버벅거릴 수도 있다

😄 code 1. 예제

const button = document.querySelector('button');
button.addEventListener('click', () => {
    const element = document.createElement('h1');
    document.body.appendChild(element);
    element.style.color = 'black';
    element.textContent = 'Q';
});
const button = document.querySelector('button');
button.addEventListener('click', () => {
    const element = document.createElement('h1');
    element.style.color = 'black';
    element.textContent = 'Q';
    document.body.appendChild(element);
});
  • 보통 사람들은 첫번째 코드는 appendChild로 인해 h1 요소가 화면에 그려진 후 색이 변하고 텍스트가 추가된다고 생각하고 두번째 코드는 h1 요소의 색과 텍스트를 먼저 스타일링 해주고 그 다음 화면에 그려진다고 생각한다

  • 그러나 위 두개의 코드 결과는 동일하다

왜?
1. Click이 발생할 때 등록한 콜백을 Task Queue에 넣어준다

2. Event Loop가 순회하면서 Task Queue에 콜백이 있는 걸 확인하고 Call Stack이 비워져 있으면 콜백을 Call Stack으로 가져온다
(이 때 코드 한줄 한줄씩 가져오는 것이 아닌 콜백 전체를 가져온다
즉 색 변경, 텍스트 추가, 요소 추가 등등의 코드들을 한가지씩 가져오는 것이 아닌 몽땅 Call Stack으로 가져와서 작업을 수행한다)

3. Call Stack에서 콜백의 작업이 끝나면 Render로 가서 Rendering 과정을 거치고 전체적으로 한꺼번에 스타일링 되어서 브라우저에 표기된다
(그래서 위 두개의 코드는 순서 상관 없이 결과가 동일한 것이다)

주의사항
1. 모든 Web APIs가 Task Queue를 이용하는 것은 아니다

2. createElement, appendChild 등 대개의 경우는 Event Loop를 이용하지 않고 즉각 실행된다

3. Task Queue로 가는 경우는 지금 당장 일을 수행하지 않아도 되는 즉 Click, Move 같은 이벤트가 발생할 때 처리되는 콜백함수나 setTimeout 등이 있다


😍 코드 지적은 언제나 환영입니다. 읽어주셔서 감사합니다. 😍

profile
지적은 제 발전의 원동력입니다. 사소한 것이라도 지적해주세요 :)

0개의 댓글