이 블로그 글은 Jake Archibald: Event Loop - JSConf.Asia 강의를 보고 정리한 내용입니다.
다음 두 코드를 비교해 보겠습니다.
document.body.appendChild(el)
el.style.display = 'none'
el.style.display = 'none'
document.body.appendChild(el)
코드 1을 보면 "요소를 먼저 추가한 다음 숨기니, 사용자가 잠깐 요소를 보게 되지 않을까?" 하는 의문이 들 수 있습니다.
하지만 실제로는 두 코드 모두 동일하게 작동합니다. 이것이 가능한 이유는 바로 이벤트 루프 덕분입니다.
웹 페이지에는 메인 스레드가 존재하며, 여기서 자바스크립트 실행과 DOM 렌더링이 모두 이루어집니다. 자바스크립트는 싱글 스레드 언어이므로 한 번에 하나의 작업만 실행할 수 있습니다.
이벤트 루프는 작업을 조율하는 역할을 합니다. 특정 작업을 태스크 큐에 할당하면 이벤트 루프가 이를 처리합니다:
settimout(func1,1000)
settimout(func1,1000)
이벤트 루프는 실행 중이던 작업이 끝나면, 태스크 큐에 있는 작업(위 코드)을 가져와 실행합니다.
브라우저의 렌더링 과정은 다음과 같은 단계로 이루어집니다:
1. JavaScript 실행 - 이벤트 핸들러, 타이머 콜백 등의 실행
2. 스타일 계산 - CSS 규칙 적용
3. 레이아웃 계산 - 요소 크기와 위치 계산
4. 페인트 - 픽셀을 화면에 그리기
5. 컴포지팅 - 레이어 합성
이 중요한 사실은 JavaScript 코드가 모두 실행된 후에만 렌더링이 일어난다는 것입니다. 따라서 앞서 본 두 코드는 모두 동일한 결과를 보여줍니다.
button.addEventListner("click",event =>{
while(true)
}
이 코드는 무한 루프로 인해 JavaScript 실행이 끝나지 않기 때문에 브라우저가 멈춥니다.
function loop(){
setTimeout(loop,0)
}
button.addEventListner("click",event =>{
loop()
}
반면, 이 코드는 브라우저를 멈추지 않습니다. 왜냐하면 setTimeout은 작업을 태스크 큐에 넣고 현재 실행 중인 작업을 마치기 때문입니다. 이벤트 루프는 한 작업을 실행한 후 렌더링 단계를 거친 다음, 다음 작업을 실행합니다.

애니메이션을 구현할 때 두 가지 방법을 비교해 보겠습니다.
function callback(){
moveBoxFoward();
requestAnimationFrame(callback);
}
callback()
function callback(){
moveBoxFoward();
setTimeOut(callback);
}
callback()

빨라서 좋은거 아닌가요
setTimeout을 사용한 코드는 콜백이 더 자주 발생하여 애니메이션 속도가 빠르게 보일 수 있습니다. 하지만 이로 인해 다음과 같은 문제가 발생합니다.
렌더링 엔진의 주기와 관계없이 진행되어 티어링 현상 발생
불필요한 계산 및 프레임 드롭 발생
반면 requestAnimationFrame은
function loop(){
Promise.resolve.then(loop)
}
button.addEventListner("click",event =>{
loop()
과거에는 이러한 코드가 여러 개의 개별 이벤트(버블링 포함)를 생성했습니다. 이를 효율적으로 처리하기 위해 MutationObserver와 마이크로태스크 큐가 도입됩니다.
function loop() {
Promise.resolve().then(loop);
}
button.addEventListener("click", event => {
loop()
})
마이크로 테스트 큐와 매크로 테스크 큐의 차이에서 발생하는 문제입니다.
마이크로 태스크들은 실행하면서 새로운 마이크로 태스크를 큐에 추가할 수도 있다. 새롭게 추가된 마이크로 태스크도 큐가 빌 때까지 계속해서 실행된다.
반대로, 이벤트 루프는 매크로 태스크 큐에 있는 것을 실행시키기 시작할 때 있는 매크로 태스크만 실행시킨다. 매크로 태스크가 추가한 매크로 태스크는 다음 이벤트 루프가 실행될 때까지 실행되지 않는다
위 비동기 함수로 브라우저 괴롭히기 코드 예시는 루프가 매크로 테스크 큐에 삽입이 되어 다음 이벤트 루프까지 실행을 시키지 않지만
마이크로 테스트 큐에 삽입된 요소들은 큐가 빌 때 까지 계속해서 실행합니다.
이러한 차이점으로 인해 같은 비동기 처리임에도 불구하고 두 코드가 다른 결과가 나옴을 알 수 있습니다.
이벤트 루프와 태스크 큐 (마이크로 태스크, 매크로 태스크) 벨로그
https://velog.io/@yejineee/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84%EC%99%80-%ED%83%9C%EC%8A%A4%ED%81%AC-%ED%81%90-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C-%ED%83%9C%EC%8A%A4%ED%81%AC-%EB%A7%A4%ED%81%AC%EB%A1%9C-%ED%83%9C%EC%8A%A4%ED%81%AC-g6f0joxx
Jake Archibald: Event Loop - JSConf.Asia 유튜브 영상
https://www.youtube.com/watch?v=cCOL7MC4Pl0
관련 강의를 정리해놓은 블로그
https://www.andreaverlicchi.eu/blog/jake-archibald-in-the-loop-jsconf-asia-talk-transposed/