이벤트 루프

JS Engine
call stack (= 실행 컨텍스트)
- JS 코드가 실행되는 곳이다.
- 함수가 호출될 때마다 스택에 push, 함수가 끝나면 pop된다.
- 한 시점에 콜 스택 가장 위에 있는 함수 하나만 실행한다.
- 콜 스택이 비어야만 이벤트 루프가 큐에 있는 콜백을 실행할 수 있다.
heap
- 객체, 배열 함수 같은 참조 타입 값들이 저장되는 메모리 영역이다.
- 구조화된 데이터들이 저장되고, 콜 스택에서는 이 데이터들의 주소만 가지고 있다.
- JS가 GC로 더 이상 쓰이지 않는 객체들을 청소하는 공간이다.
Web API
- 브라우저가 제공하는 기능 묶음
- 타이머를 재거나, 네트워크 요청을 보내거나, 클릭을 감지
- Web API는 비동기 작업을 실제 수행하는 곳
- 작업이 끝났을 때 실행되어야 할 콜백은 큐에 넣는다.
microtask queue
- 어떤 것들이 들어가는가
- Promise
- await 뒷 부분
- queueMicrotask(...)
- MutationObserver 콜백
- 콜 스택에 비어졌을때 실행
- 마이크로태스크 큐에 있는 것들을 전부 실행한다.
- 한 번 스택이 비면, 마이크로태스크 큐를 완전히 비울 때까지 계속 실행한다.
macrotask queue
- 어떤 것들이 들어가는가
- setTimeout, setInterval 콜백
- DOM 이벤트 콜백
- 네트워크 요청 완료 콜백 (ex: fetch then 내부의 첫 task)
- Web API가 일을 다 끝낸 후에 이제 이거 실행해 하고 넣는 함수들이 모인 곳
- 매크로 태스크 큐 콜백 딱 하나를 꺼내서 콜 스택에서 실행.
렌더링 파이프 라인
- HTML text, CSS text -> 파싱 -> Dom tree, CSSOM tree
- Render tree
- DOM tree와 CSSOM tree를 합쳐 화면에 실제 그릴 노드들만 모은 트리를 만든다.
- display: none과 같은 실제 보이지 않는 노드들 제외
- Layout (Reflow)
- render tree의 각 노드가 화면 어디에, 어떤 크기로 배치될지 계산
- Painting (Rasterization)
- 레이아웃 계산을 바탕으로 실제 픽셀을 칠하는 단계
- 색, 테두리, 그림자, 이미지, 텍스트 등이 그려짐
- 왜 JS 실행 후, 매크로태스크 전에 페인팅을 끼워 넣나?
- 최종 상태 한 번만 그리기 위해
- JS 코드가 DOM을 막 변경하고 업데이트 함
- JS task 실행 -> microtask로 자잘한 후속작업 마저 처리 -> DOM/CSS 안정 -> paint -> 다음 task
Event Loop
- 콜 스택이 비었는지 확인
- 비어 있다면
- 마이크로 태스큐에 있는 것들 전부 실행
- 렌더링 단계(페인트) 진행 -> 필요한 경우
- 매크로 태스큐에서 task 하나를 꺼내서 콜 스택에 올리고 실행
- 다시 1번으로 돌아감
전체 흐름
console.log("1");
button.addEventListener("click", () => {
console.log("click");
});
setTimeout(() => {
console.log("timeout");
Promise.resolve().then(() => {
console.log("promise in timeout");
});
}, 0);
console.log("2");
1단계: 초기 스크립트 실행 (콜 스택)
console.log("1") → 출력: 1
addEventListener → Web API(DOM)에 콜백 등록만 하고 끝
setTimeout(...) → Web API 타이머에 작업을 맡기고 스택에서 빠짐
console.log("2") → 출력: 2
- 초기 스크립트 끝 → 콜 스택 비어짐
2단계: microtask queue
- 아직
Promise 관련 코드가 없음 → microtask queue는 비어 있음
→ 아무 일도 안 함
3단계: 렌더링
- 필요하면 렌더링 한 번 할 수 있음 (화면 업데이트)
4단계: macrotask queue에서 task 하나 실행
- 0ms 지난 후 Web API의 타이머가 끝나면
setTimeout 콜백이 macrotask queue에 들어간다.
- 이벤트 루프가 이 콜백 하나를 꺼내서 콜 스택에 올리고 실행:
console.log("timeout") → 출력: timeout
Promise.resolve().then(...) 실행 → then 콜백이 microtask queue에 들어감
setTimeout 콜백 함수 끝 → 콜 스택 비어짐
5단계: 다시 microtask queue 비우기
- 방금 등록된
promise in timeout 콜백 실행 → 출력: promise in timeout
- microtask queue 비어짐
6단계: 렌더링
7단계: 이후
- 사용자가 버튼을 클릭하면, Web API(DOM)가 그 순간
click 핸들러를 macrotask queue에 넣는다.
- 이벤트 루프는 다시 위 순서대로 돌다가, macrotask queue에서
click 콜백을 꺼내 실행한다.
→ console.log("click") 출력