JavaScript의 런타임 모델
자바스크립트는 싱글 스레드 기반의 언어이다.
하나의 스레드 = 하나의 콜 스택 = 한번에 하나의 작업
입력된 호출을 순차적으로 실행한다. 앞선 코드가 다 실행되기 전까지 다른 코드를 실행시키지 않는다.
자바스크립트는 웹브라우저 상에서 동작한다. 실행하기 까지 오래걸리는 A라는 코드가 존재하면 나머지 화면 렌더링이라던가 다른 동작해야하는 모든 코드가 A코드가 실행을 마칠 때까지 기다려야 한다.(블로킹 현상)
이벤트 루프
이벤트 루프는 코드의 실행, 이벤트의 수집과 처리, 비동기 함수들의 처리를 담당하는 메커니즘이다.
동기 함수는 그대로 실행하게 되고 비동기 함수들은 Web API로 처리하여 일을 분배한다.
자바스크립트의 싱글 쓰레드 특성을 유지하면서 비동기적인 동시성을 제공한다.
자바스크립트의 런타임 모델은 코드의 실행, 이벤트의 수집과 처리, 큐에 대기 중인 하위 작업을 처리하는 이벤트 루프에 기반하고 있다.
콜스택
스택은 후입선출을 기본으로 하는 자료구조이다.(마지막에 들어간 게 먼저 나옴)
자바스크립트 엔진이 구동되면서 실행 중인 코드를 추적하는 공간이다.
콜스택 오버플로우
function overflow() {
overflow()
}
overflow()
위와 같은 재귀호출 코드를 실행시키면
Maximum call stack size exceeded와 같이 콜스택 사이즈가 초과되었다는 오류를 볼 수 있다.
콜스택의 사이즈를 초과하면 더 이상 함수를 실행할 수 없고 오류가 발생하며 종료된다.
힙
자바스크립트 엔진이 구동되면서 변수,함수 같은 정보를 저장하는 곳이 메모리 힙이다. 메모리 할당이 일어나는 장소이다. 함수의 실행이 끝나고 스택 프레임이 제거된 이후에도 힙에 저장된 객체들은 여전히 유지 될 수 있다.
이러한 특징으로 중첩 함수에서는 힙에 있는 변수와 함수에 접근할 수 있게 된다.(이러한 특성을 클로저라고 한다)
큐
선입선출의 특징을 갖는 자료구조다.(먼저 들어간게 먼저 나온다.)
자바스크립트의 런타임 환경의 이벤트 큐는 처리할 메세지 목록과 실행할 콜백 함수들을 갖는다.
스택이 텅 비어있게 되면 큐에 남아있는 이벤트를 오래된 것 부터 순서대로 호출하기 시작한다.
콜백큐
비동기적으로 실행된 콜백 함수가 보관되는 곳이다.
3가지의 종류가 있다.
1. Task Queue
setTimeout, setInterval
2. Microtask Queue
Promise callback, async callback
3. Animation Frames
requestAnimationFrame
"Microtask Queue > Animation Frame > Task Queue" 순으로 Microtask Queue가 가장 먼저 실행되고 Task Queue가 가장 늦게 실행된다.
이벤트 루프의 동작 과정
- 자바스크립트 엔진이 코드를 순차적으로 실행한다. 이벤트 루프는 비동기 작업들이 발생하는 것을 감지하고 이벤트를 수집한다.
- 콜스택을 확인하고 비어있다면 실행할 작업이 남아있는 지 체크한다.
- 콜백큐 확인 비동기 작업의 콜백 함수들이 콜백 큐에 들어온 순서대로 대기하고 있는지 확인한다.
- 콜 스택이 비어있고, 콜백큐에 콜백 함수들이 대기하고 있다면 콜스택에 가장 오래된 콜백 함수를 콜 스택으로 이동시킨다.
- 콜백 함수를 실행한다.
- 위의 과정을 반복한다.
비동기적으로 호출되는 콜백 함수 예시
둘 다 forEach 를 사용하지만 첫번째의 경우는 계속 콜 스택에 쌓이는 방식이고 두 번째의 경우는 트릭을 써서 Web API를 활용해 콜백 큐로 이동시켰다가 이벤트 루프를 통해 콜 스택으로 옮기는 방식이다.
// Synchronous
[1,2,3,4].forEach(function(i) {
console.log(i);
});
// Asynchronous
function asyncForEach(array, cb) {
array.forEach(function(){
setTimeout(cb, 0);
});
}
asyncForEach([1,2,3,4], function(i){
console.log(i);
});
이벤트 루프가 필요한 이유
앞서 언급된 블로킹 현상을 해결하기 위함이다.
Blocking에 대한 정확한 정의는 없지만 “느리게 동작하는 코드” 로 정의할 수 있다. 즉, 콜 스택에 현재 느리게 동작하는 작업이 남아있는 것을 말한다. 네트워크 요청 혹은 이미지 프로세싱 등을 예시로 들 수 있다.
느린 작업으로 인해 blocking이 발생하게 되면 웹 브라우저는 렌더링을 하지 못하고 다른 코드 또한 실행할 수 없게 된다. UI/UX적으로 좋지 못한 경험을 제공하게 된다.
비동기 콜백을 통한 블로킹 완화
웹 브라우저는 1초에 60 프레임을 다시 그리는게 가장 이상적인 경우라고 한다.
렌더링 또한 콜백 처럼 작용해서 콜 스택에 들어가게 된다. 하지만 콜 스택에서 어떤 작업이 지연되고 있을 경우 렌더링을 못한다. 느린 작업이 동기적으로 콜 스택에 있게 되면 렌더링을 못하게 되고 그에 따라 UI가 블로킹 되는 현상이 발생한다.
렌더링을 할 때는 렌더 큐(Render Queue) 또한 존재해서 이벤트 루프가 콜백 큐를 감시하는 것과 비슷한 방식으로 콜 스택이 비었을 경우 렌더링을 시도한다. 비동기 콜백으로 작업을 실행하게 되면 콜 스택이 비는 때가 존재하기 때문에 렌더링이 될 수 있는 틈을 준다. 이것이 바로 비동기 콜백을 사용하는 이유이다.
참고자료
[JS] 도대체 이벤트 루프가 뭔가요? - 배하람의 블로그
Javascript Event Loop 이벤트 루프 정리
What the heck is the event loop anyway? | Philip Roberts | JSConf EU