TIL: 자바스크립트 비동기와 event loop, requestAnimationFrame() - 220626

Lumpen·2022년 6월 26일
0

TIL

목록 보기
70/242
post-thumbnail

자바스크립트는 싱글 스레드 언어다
자바스크립트 엔진의 이벤트 루프를 통해 비동기 작업이 가능

스레드는 어떠한 프로그램이 실행되는 작업을 말한다.
싱글 스레드는 한 번에 하나의 작업만 수행할 수 있으며, 멀티 스레드는 한 번에 여러 개의 작업을 수행할 수 있다.

자바스크립트의 메모리 힙, 콜스택

메모리 힙

메모리 힙: 메모리 할당이 일어나는 곳

메모리 힙에는 참조 타입(객체 등) 데이터가 저장된다
힙은 갑자기 데이터가 커질 수도 있는 (동적인)
배열과 객체와 같은 정렬되지 않은 데이터들을 저장할 수 있다
메모리 할당이 일어나는 곳 - 선입 선출
참조타입 데이터가 저장된 메모리 힙의 주소값은 콜스택에 각각 저장된다
메모리힙의 주소 값이 저장된 콜 스택의 주소값은 각각 변수 b, c, d에 저장된다
변수 식별자 b, c, d 이름 자체는 콜스택 상의 '실행 컨텍스트(Execution Context)의 렉시컬 환경(Lexical Environment)'에 저장된다

콜스택

https://garychang.gitbook.io/data-structure/lecture1-stack-and-queue/lecture1.1-stack-dui

콜 스택: 힙에 저장된 객체를 참조하여, 호출 된 코드(함수)의 정보를 저장하고 실행하는 곳
콜스택 은 원시타입과 함수 호출이 저장되는 공간
스택은 후입선출(LIFO, Last In First Out)의 특징을 갖고 있다

콜 스택에 while(true)나 React useEffect()의 두 번째 인자를 작성하지 않아 무한 호출되는 함수가 존재한다면, 콜 스택이 감당할 수 있는 범위를 초과하면 브라우저의 동작이 멈추니 주의해야 함

자바스크립트 엔진의 이벤트 루프

이벤트 루프는 실행 할 함수를 관리하는 역할로 콜 스택과 큐(Queue)의 함수를 계속 확인한다
만약 콜 스택이 비어 있고 큐에 대기 중인 함수가 있다면,
순차적으로 큐에 대기중인 함수를 콜 스택으로 이동시킨다
그리고 이렇게 반복되는 매 순회(Iteration)을 tick이라 부른다.

https://blog.sessionstack.com/how-javascript-works-event-loop-and-the-rise-of-async-programming-5-ways-to-better-coding-with-2f077c4438b5

콜 스택에서 Web APIs를 거쳐 큐로 넘어간다
이는 어떤 함수나 이벤트가 종료될 때까지 시간이 오래 걸릴 수 있기 때문에
자바스크립트 엔진이 직접 처리하는 것이 아니라 브라우저에 위임한다

Web APIs에서 연산을 마친 후 콜 스택에서 바로 실행될 수 있는 상태가 되었을 때 큐에 등록한다
큐는 선입선출(FIFO, First In First Out)을 갖고 있다

https://garychang.gitbook.io/data-structure/lecture1-stack-and-queue/lecture1.2-queue-lie

마이크로 태스크 큐(Micro Task Queue)

Promise()의 후속 처리 메서드의 콜백 함수나 MutationObserver()가 대기하는 곳이다.

rAF 큐(Request Animation Frame Queue)

requestAnimationFrame()처럼 애니메이션을 업데이트하는 콜백 함수가 대기하는 곳이다

태스크 큐(Task Queue)

setTimeout(), setInterval()과 같은 비동기 함수의 콜백 함수 또는 이벤트 핸들러가 대기하는 곳

큐 우선순위

마이크로 태스크 큐 > rAF 큐 > 태스트 큐 순서

브라우저에 따라 태스크 큐와 rAF 큐의 실행 순서가 달라질 수 있다

긴급 실행

태스크 큐보다 먼저 실행해야하는 함수가 존재한다면
queueMicrotask()로 우선 순위를 끌어올릴 수 있다

const callback = () => console.log('일반 콜백 함수 호출')
const urgentCallback = () => console.log('---> 긴급 콜백 함수 호출')

console.log('시작')
setTimeout(callback, 0)
queueMicrotask(urgentCallback)
console.log('종료')

// 시작
// 종료
// 긴급
// 일반

requestAnimationFrame()

setTimeout(setInterval)의 문제
대부분의 브라우저는 W3C 권장사항에 따라 디스플레이 주사율과 일치한 횟수로 콜백 함수를 호출한다
그래서 보통 60FPS로 화면을 렌더링하고
이는 콜백의 수가 보통 1초에 60회, 16ms(0.016초)에 하나씩 실행된다

그러나 setTimeout()과 setInterval() 모두 시간 기반의 함수가 아니고
앞의 콜백 함수가 종료 시에 실행된다
만약 컴퓨터 성능이 안 좋다면 프레임(16ms) 시작 때 함수 실행이 늦어져
화면과 싱크가 맞지 않아 애니메이션이 끊겨 보이는 현상이 일어난다

https://namu.wiki/w/FPS

반면 requestAnimationFrame()는 콜백을 실행하는 시점에
DOMHighResTimeStamp가 전달되어 시간 기반으로 작동하는 함수로
프레임(16ms) 시작 때 실행을 보장한다
때문에 무한 스크롤 등을 구현할 때 setTimeout() 기반의 throttle 대신 requestAnimationFrame()을 사용해야 한다.

https://web.dev/optimize-javascript-execution/

실행 순서 정리

//1. script 실행 (log)
console.log("script start");

//2. script 실행 (setTimeout callback task queue에 등록)
setTimeout(function () {
  //11. Task 실행
  console.log("setTimeout");
}, 0);

//3. script 실행 (Promise then callback Microtask queue에 등록)
Promise.resolve()
  .then(function () {
    // 7. MicroTask 실행
    console.log("promise1");
  }) // 8. script 실행 (Promise then callback Microtask queue에 등록)
  .then(function () {
    // 9. MicroTask 실행
    console.log("promise2");
  });

//4. script 실행 (AnimationFrame Animation frames에 등록)
requestAnimationFrame(function () {
  //10. Animation Frame 실행
  console.log("animation");
});

//5. script 실행
console.log("script end");
//6. Stack의 모든 Task 실행완료
script start
script end
promise1
promise2
animation
setTimeout

https://www.howdy-mj.me/javascript/asynchronous-programming/

https://charming-kyu.tistory.com/19

https://iamsjy17.github.io/javascript/2019/07/20/how-to-works-js.html

profile
떠돌이 생활을 하는. 실업자는 아니지만, 부랑 생활을 하는

0개의 댓글