자바스크립트는 싱글 스레드이다. 즉, 1개의 call stack을 가지고 있고, 따라서 동시에 한가지 일만 할 수 있다.
하지만 이벤트 루프를 통해 브라우저는 HTML 요소를 애니메이션 효과를 통해 이벤트를 처리하거나, HTTP 요청을 통해 서버로부터 데이터를 가져온 후 렌더링 등 동시성을 지원해준다.
콜스택에 현재 실행 중인 실행 컨텍스트가 있는지, 그리고 태스크 큐에 대기 중인 함수가 있는지 반복해서 확인한다. 만약 콜스택이 비어있고, 태스크 큐에 대기 중인 함수가 있다면 이벤트 루프는 순차적으로 태스크 큐에 대기 중인 함수를 콜스택으로 이동시키며, 이동된 함수는 실행이 된다.
또한 태스크 큐와 마이크로 태스크 큐에 대기 중인 함수가 있다면, 마이크로 태스크 큐를 우선적으로 처리 후 태스크 큐를 처리하게 된다.
이벤트 루프는 브라우저 내장 기능 중 하나이며, Node에서는 LIBUV가 있다.
// 이벤트 루프의 구현
// 현재 처리할 수 있는 메시지가 없으면 새로운 메시지가 도착할 때 까지 대기
while (queue.waitForMessage()) {
queue.processNextMessage();
}
실행 컨텍스트가 추가되고 제거되는 스택 자료구조
GEC → FEC 가 순차적으로 콜스택에 푸시되어 순차적으로 실행되고, 자바스크립트 엔진은 단 하나의 콜스택만 사용하기 때문에 최상위 실행 컨텍스트가 종료되어 콜스택에서 제거되기 전까지는 다른 어떤 태스크도 실행되지 않는다.
객체가 저장되는 메모리 공간으로, 콜스택의 요소인 실행 컨텍스트는 힙에 저장된 객체를 참조한다.
메모리에 값을 저장하려면 먼저 값을 저장할 메모리 공간의 크기를 결정해야 하는데, 객체는 원시 값과는 달리 크기가 정해져 있지 않아 할당해야 할 메모리 공간의 크기를 런타임에 결정해야한다. (동적 할당)
따라서 객체가 저장되는 메모리 공간은 구조화 되어있지 않다.
DOM, AJAX, Timer, Event Handler 등 브라우저가 제공하는 API들로써, 비동기적으로 실행되는 작업을 전담하여 처리 후 콜백 함수를 callback queue로 전달한다.
브라우저에서 멀티 스레드로 구현되어 있어 동시에 처리가 가능하다
<canvas>
요소를 통해 그래픽을 그리거나 애니메이션을 만들 수 있는 메소드 제공Callback Queue는 (macro)task queue와 microtask queue를 묶어 칭하는 개념이다.
setTimeout, setInterval과 같은 비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 저장되는 공간이다.
주로 Timer API, fetch API, addEventListner와 같이 비동기로 처리되는 함수들의 콜백 함수가 들어간다.
task queue와는 별도로 프로미스의 후속 처리 메서드의 콜백 함수가 일시적으로 저장되는 공간이다.
주로 promise.then, process.nextTick, MuttaionObserver와 같이 우선적으로 비동기로 처리되는 함수의 콜백 함수가 들어간다.
같은 Queue안에 적재되는 콜백이라도 어떤 비동기 작업이냐에 따라 우선순위가 다른 태스크들이 있을 수 있다. ex) microtask queue에 적재되는 promise와 mutation observer 콜백 중에서는 mutation observer가 먼저 처리된다.
브라우저의 Queue는 콜백 큐 뿐만 아니라 브라우저 애니메이션 작업에 대한 처리를 담당하는 AnimationFrame Queue가 있다.
JS에서 애니메이션 동작을 제어하는 requestAnimationFrame
메서드를 통해 콜백을 등록하면, 이 큐에 적재되어 브라우저가 repaint 직전에 AnimationFrame Queue에 있는 작업들을 전부 처리한다.
스타일 관련 코드를 비동기로 처리하도록 구성하여 애니메이션 타이밍 관리, 적절한 프레임 속도 등으로 애니메이션 동작의 성능과 품질을 향상시킬 수 있다.
마이크로 태스크 큐와 매크로 태스크 큐의 우선순위를 어떻게 정하느냐가 큰 차이점이다.
아래 코드를 보고 Node v11 이상 버전과 이전 버전의 결과가 어떻게 될 지 예상해보자
Promise.resolve().then(() => console.log('promise1 resolved'))
Promise.resolve().then(() => console.log('promise2 resolved'))
setTimeout(() => {
console.log('set timeout3')
Promise.resolve().then(() => console.log('inner promise3 resolved'))
}, 0)
setTimeout(() => console.log('set timeout1'), 0)
setTimeout(() => console.log('set timeout2'), 0)
Promise.resolve().then(() => console.log('promise4 resolved'))
Promise.resolve().then(() => {
console.log('promise5 resolved')
Promise.resolve().then(() => console.log('inner promise6 resolved'))
})
Promise.resolve().then(() => console.log('promise7 resolved'))
브라우저의 동작과 일치
마이크로 태스크 큐가 매크로 태스크 큐에 비해 우선순위가 더 높다.
promise1 resolved
promise2 resolved
promise4 resolved
promise5 resolved
promise7 resolved
inner promise6 resolved
set timeout3
inner promise3 resolved
set timeout1
set timeout2