
이벤트 루프란?
자바스크립트는 싱글 스레드로 동작한다.
💡 싱글 스레드란, 한 번에 하나의 태스크만 처리할 수 있는 방식
그러나, 브라우저의 동작을 보면 많은 태스크가 동시에 처리되는 것처럼 느껴지는데, 이처럼 자바스크립트의 동시성을 지원하는 것이 이벤트 루프이다. 이벤트 루프는 브라우저에 내장되어 있는 기능 중 하나이다.
자바스크립트 엔진은 싱글 스레드로 동작하지만 브라우저는 멀티 스레드로 동작한다는 것에 주의해야한다. 모든 자바스크립트 코드가 자바스크립트 엔진에서 싱글 스레드 방식으로 동작한다면 자바스크립트는 비동기로 동작할 수 없다. 그렇게 된다면 setTimeout
만 봐도, 타이머 대기 시간동안 그 어떤 태스크도 수행할 수 없기 때문이다.
자바스크립트 엔진 영역
소스코드 평가 과정에서 생성된 실행 컨텍스트가 추가되고 제거되는 실행 컨텍스트 스택
-
힙
객체가 저장되는 메모리 공간. 객체는 원시 값과 달리 크기가 정해져 있지 않아 할당해야 할 메모리 공간의 크기를 런타임에 동적 할당 해야하므로 힙은 구조화되어 있지 않음
실행 컨텍스트란
식별자(변수, 함수, 클래스 등의 이름)을 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 메커니즘으로, 모든 코드는 실행 컨텍스트를 통해 실행되고 관리된다. 식별자와 스코프는 렉시컬 환경으로 관리하고, 코드 실행 순서는 실행 컨텍스트 스택으로 관리한다. 자바스크립트 엔진은
- 전역 코드 평가 실행 컨텍스트 생성 및 실행 컨텍스트 스택에 푸시
- 전역 코드 실행
- 함수 코드 평가 및 실행
...
으로 동작하는데, 평가 단계에서는 선언문만 실행하고 이후 실행단계(런타임)에서는 코드가 순차적으로 실행된다. 이때 변수에 값이 할당되고 함수가 호출된다.
브라우저 환경
`setTimeout`이나 `setInterval`과 같은 비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역
-
이벤트 루프
콜 스택에 현재 실행중인 실행 컨텍스트가 있는지, 태스크 큐에 대기 중인 함수가 있는지 반복해서 확인. 콜 스택이 비어 있고 태스크 큐에 대기 중인 함수가 있다면 이벤트 루프는 순차적으로 태스크 큐에 대기 중인 함수를 콜 스택으로 이동. 즉, 태스크 큐에 보관된 함수들은 비동기 방식으로 동작
-
마이크로태스크 큐
프로미스의 후속 처리 메서드의 콜백 함수 일시 저장. 마이크로태스크 큐는 태스크 큐보다 우선순위가 높음. 즉, 이벤트 루프는 콜 스택이 비면 마이크로태스크 큐에서 대기하고 있는 함수를 가져와 실행
동작예시
const a = () => {
b();
console.log("a");
};
const b = () => {
console.log("b");
};
setTimeout(() => {
console.log("c");
}, 500);
a();
-
전역 코드 평가 -> 전역 실행 컨텍스트가 콜 스택에 푸시됨
-
전역 코드 실행, setTimeout이 호출되어 함수 실행 컨텍스트 생성 -> 콜 스택에 푸시됨
-
setTimeout 함수 실행 -> 콜백 함수 호출 스케줄링 후 콜스택에서 팝 되고 타이머가 만료되면 태스크 큐에 푸시(브라우저가 이행)
-> 5 ms 이후에 태스크 큐에서 대기. 태스크 큐에 있는 작업은 콜 스택이 비어야 호출되므로 시간차가 발생할 수 있음. 즉, 콜백 함수 실행까지 5 ms 이상 걸릴 수 있음
-
(브라우저가 실행하는 타이머&태스크 큐 푸시 작업과 함께 처리됨) a 함수 호출, a 함수 실행 컨텍스트 생성 및 콜 스택 푸시
-
b 함수 호출, b 함수 실행 컨텍스트 생성 및 콜 스택 푸시 -> b 콘솔 출력 -> b 실행 컨텍스트 스택에서 팝
-
a 콘솔 출력 -> a 실행 컨텍스트에서 팝
-
전역 실행 컨텍스트 팝 -> 콜 스택 비워짐
-
5 ms가 지났다면, 콜 스택으로 태스크 큐에 있던 setTimeout의 콜백 함수가 이벤트 루프에 의해 이동 -> 함수 실행 컨텍스트 생성 후 콜 스택 푸시, 실행 후 팝
setTimeout(fn, 0)
종종 이런 코드를 보게되는데, '0초 후에 실행? 그냥 실행하는 거랑 다른 거 아니야?'라는 생각을 하게 된다. 그냥 실행하는 거랑 뭐가 다를까?
setTimeout
의 경우 콜백함수를 바로 실행하지 않고, 콜스택이 아닌 태스크큐에 추가한다. 그렇기 때문에 아래의 코드는 B가 먼저 출력되고 A가 나중에 출력될 것!
setTimeout(function () {
console.log("A");
}, 0);
console.log("B");
그럼 0초는 거짓말인가? 싶어서 찾아봤는데, 브라우저는 setTimeout
호출이 5번 이상 중첩된 경우 4ms의 최소 타임아웃을 강제한다고 한다. 그래서 첫 네 번까지는 콜백함수 실행까지의 딜레이가 0 ms에 근접하지만, 그 이후부터는 약 4 ms정도 딜레이된다고 한다.