Memory Heap
메모리 힙은 메모리가 동적으로 할당되는 공간으로, 객체나 변수의 데이터가 저장되는 곳이다. 메모리 힙에서는 할당과 해제가 이루어지며, 개발자가 명시적으로 관리하지 않아도 된다.
Call Stack
콜 스택은 코드 실행 흐름을 관리하는 곳이다. 함수 호출이 발생할 때 호출된 함수가 스택에 추가되고, 실행이 끝나면 스택에서 제거된다. 이는 후입선출(LIFO, Last In First Out) 구조로 동작하며, 가장 나중에 추가된 작업이 가장 먼저 실행된다.
콜 스택의 동작 방식
function first() {
console.log("First");
}
function second() {
first();
console.log("Second");
}
function third() {
second();
console.log("Third");
}
third();
function first() {
console.log("First");
}
function second() {
first();
console.log("Second");
}
function third() {
second();
console.log("Third");
}
third();
// 결과
First
Second
Third
자바스크립트 런타임 환경
자바스크립트는 브라우저나 Node.js 환경에서 실행되며, 이러한 환경을 **런타임(Runtime)**이라고 한다. 런타임 환경은 자바스크립트 코드 실행뿐만 아니라 다양한 작업을 수행할 수 있도록 여러 요소를 제공한다.
주요 구성 요소
- Web API
- 브라우저에서 제공하는 API로, 자바스크립트 엔진 외부에서 동작한다.
- 예: setTimeout, DOM 이벤트, AJAX 요청.
- 이러한 API는 호출 시 브라우저에 의해 관리되며, 자바스크립트 엔진에 의해 직접 정의되지 않았다.
- Task Queue
- 비동기 작업이 완료된 후 실행 대기 중인 콜백 함수들이 기다리는 공간이다.
- 콜백 큐(Callback Queue)라고도 하며, 이벤트 루프에 의해 처리 순서가 결정된다.
- Event Loop
- 이벤트 루프는 현재 실행 중인 작업(Call Stack)이 비었는지 확인한 후, 비어 있다면 Task Queue에서 대기 중인 작업을 Call Stack으로 이동시킨다.
- 이 과정이 반복적으로 실행되어 자바스크립트의 비동기 작업 처리를 가능하게 한다.
자바스크립트의 비동기 처리
비동기 처리란?
비동기 처리는 특정 코드가 실행되는 동안 해당 코드의 완료를 기다리지 않고 다음 코드를 먼저 실행하는 방식이다. 자바스크립트는 싱글 스레드 기반 언어이지만, 비동기 처리 기법을 통해 병렬 처리가 가능하다.
비동기 처리 방식의 예시
console.log("1");
setTimeout(() => {
console.log("2");
}, 1000);
console.log("3");
// 결과
1
3
2
Task Queue의 종류
자바스크립트의 비동기 처리 과정에서 브라우저에는 여러 종류의 Queue가 존재하며, Event Loop는 이 Queue들 사이에서 우선순위를 조정하여 어떤 작업을 먼저 처리할지 결정합니다.
1. Task Queue (Macrotask Queue)
- 기존에 알고 있던 Task Queue는 이제 Macrotask Queue라고 부르기도 합니다.
- 이 큐에는 다음과 같은 작업들이 들어갑니다:
- setTimeout()
- setInterval()
- setImmediate() (Node.js에서 사용)
- DOM 이벤트 (예: click, keydown 등)
- XMLHttpRequest 콜백
- 특징:
- Macrotask Queue에 쌓인 작업은 Call Stack이 비어 있는 상태에서 차례대로 처리됩니다.
- 우선순위는 Microtask보다 낮습니다.
2. Microtask Queue(Job Queue)
- Microtask Queue는 Promise, async/await, 또는 특정 비동기 호출에서 생성된 작업을 처리합니다.
- Microtask Queue에 들어가는 작업:
- Promise의 then() 콜백
- async/await에서의 비동기 작업
- process.nextTick() (Node.js에서 사용)
- MutationObserver (DOM 변경 감지)
- 특징:
- Microtask는 Macrotask보다 우선적으로 처리됩니다.
- Event Loop는 Macrotask 실행 중에도, 각 작업이 끝날 때마다 Microtask Queue를 우선 확인합니다.
Event Loop의 역할
- Call Stack이 비어 있는지 확인합니다.
- Microtask Queue의 작업들을 먼저 처리합니다.
- Microtask Queue가 비어 있다면, Macrotask Queue에서 작업을 가져와 실행합니다.
- 다시 Microtask Queue를 확인하고, 작업이 있다면 처리합니다.
- 이러한 과정을 반복하면서 비동기 작업을 처리합니다.
- Call Stack > Microtask Queue > Macrotask Queue 실행순서
Microtask가 Macrotask보다 우선 처리되는 이유
Microtask는 현재 실행 중인 작업(또는 Macrotask)이 끝나면 바로 처리되기 때문에 즉각성이 요구되는 작업에서 주로 사용됩니다.
- Macrotask:
- 일반적인 비동기 작업 (setTimeout, setInterval 등).
- UI 업데이트 이후 실행되어도 되는 작업.
- Microtask:
- 즉각 처리가 필요한 작업 (Promise, async/await).
- 비동기 작업 결과를 빠르게 처리해야 할 때 사용.
여기서 Macrotask가 마지막에 실행되는 이유는 UI 업데이트 즉, 브라우저가 뷰를 렌더링하는 과정도 매우 중요한 부분 이유이다.
이 과정에서 *애니메이션 프레임(Animation Frame)**이 중요한 역할을 한다.
애니메이션 프레임 (requestAnimationFrame)
**requestAnimationFrame**은 브라우저가 화면을 다시 그릴 시점에 맞춰 콜백 함수를 실행할 수 있게 해주는 API입니다.
왜 애니메이션 프레임이 필요할까?
- 브라우저는 초당 약 60프레임(60fps)으로 화면을 갱신하려고 시도합니다.
- 이 과정에서 화면을 효율적으로 그리기 위해 브라우저는 렌더링 타이밍에 맞춰 애니메이션 코드를 실행해야 합니다.
- setTimeout이나 setInterval로 애니메이션을 구현하면, 프레임 타이밍과 맞지 않아 끊기거나 성능이 저하될 수 있습니다.
- requestAnimationFrame은 브라우저의 리플로우와 리페인트 과정 직전에 호출되어 최적화된 애니메이션을 제공할 수 있습니다.
requestAnimationFrame의 특징
- 브라우저가 다음 프레임을 렌더링하기 전에 실행되므로 애니메이션의 정확도가 높음.
- 브라우저가 비활성화 상태(예: 탭이 백그라운드로 이동)일 때 실행이 중단되므로 CPU 자원을 절약.
Task 우선 순위
만약 코드에서 Promise, requestAnimationFrame, setTimeout을 모두 실행시키면, 이벤트 루프가 각 Queue에 있는 Task들은 다음과 같은 순서로 처리하도록 할 것이다.
- 처음에 Call Stack에 쌓여있는 Task를 모두 처리.
- MicroStack Queue에 쌓여있는 Task를 모두 처리.
- Animation Frames에 쌓여있는 Task를 처리.
- MacroStack Queue에 쌓여있는 Task를 하나씩 처리.
MicroStack Queue(렌더링전에 실행) → Animation Frames(렌더링을 주기적으로 해줌) → MacroStack Queue(렌더링 후 실행)
그럼 Microtask(Promise, async/await)이 어떻게 실행되는지 알아보자.
참고자료