기본적으로 자바스크립트는 싱글 스레드
언어이다. 따라서 자바스크립트 엔진은 단 하나의 실행 컨텍스트 스택을 가지며, 한 번에 하나의 작업만 수행할 수 있다는 것이다.
처리에 시간이 걸리는 작업을 실행하는 경우에는 blocking이라는 작업 중단 현상이 발생하기도 한다.
하지만, 자바스크립트가 사용되는 브라우저를 생각해보면, 애니메이션 효과를 보여주면서, 마우스입력을 받는 등의 여러 개의 일을 동시에 진행한다.
이러한 자바스크립트에서 이벤트 처리는 어떻게 이루어지는 걸까??
Call Stack이 비어있고, Task Queue에 대기 중인 함수가 있다면 Event Loop가 FIFO 방식으로 Task Queue에 대기 중인 함수를 Call Stack으로 이동시킨다.
자바스크립트의 동시성을 지원해주는 것이 바로 Event Loop이다.
Event Loop는 브라우저에 내장되어 있는 기능 중 하나이다.
자바스크립트 엔진은 2개의 영역으로 구분된다.👇
Call Stack
소스 코드가 실행되면 이곳에 push되고, 종료 후에는 pop되는 스택 자료구조인 실행 컨텍스트 스택을 말한다.
Heap
객체가 저장되는 메모리 공간으로, Call Stack의 요소인 실행 컨텍스트가 여기에 저장된 객체를 참조하여 값을 가져온다.
이벤트가 발생되었을때, 처리되는 과정은 다음과 같다.
호출 스택
에 쌓인 후, 실행되면 자바스크립트 엔진은 비동기 작업을 Web API에게 넘긴다.비동기 작업
을 수행한다.Callback
함수들을 Task Queue
에 전달한다.CallStack
에 넣어준다.제거
된다.스택 / 큐에 개념이 정리되어있다.
그림을 보면 알 수 있듯이 작업이 요청되면, call Stack을 통해 작업을 순차적으로 실행한다.
즉, 콜백 함수의 평가과 실행은 자바스크립트 엔진이 담당한다는 것이다.
❗️ 하지만 ❗️
호출 스케쥴링을 위한 타이머 설정과 콜백 함수 자체의 등록은 브라우저나 Node.js가 담당하기에 브라우저 환경은 Task Queue와 Evnet Loop를 제공해야 한다.
호출 스택은 프로그램 상에서
어떤 순서
로 작업을 수행하는지 기록하는 작업 스케쥴링이랑 관련된 자료 구조이다.
어떤 함수를 실행하게 되면, 그 함수를 스택에 맨 위로 올려놓는 push
가 수행된다.
이후, 함수가 값을 리턴하거나 실행을 종료하면 해당 함수를 스택 맨 위에서 꺼내는 pop
이 수행된다.
예를 들면
function add(a,b){
return a + b;
}
function multiply(a,b){
return a * b;
}
function square(n){
return multiply(n,n);
}
function printSquare(n){
const squared = square(n);
console.log(squared);
}
printSquare(2);
add(1,2);
위 코드가 실행되어 callStack에 쌓이는 순서는 다음과 같다.
- main();
- printSquare(2);
- square(n);
- multiply(n,n);
2-4가 제거된 후- add(1,2);
일단 모든 함수들을 가지고 있는 main( )함수를 스택에 넣어주고, printSquare(2)를 스택에 넣으면, 그 내부에서 square(n)을 스택에 넣어주고, 또 그 내부에서 multiply(n,n)을 스택에 넣어준다.
printSquare가 모든 작업을 다 끝낸 후에
즉, 2~4까지 pop
이 진행되면, 마지막으로 add(1,2)를 스택에 넣고, 이과정이 끝나면 main()이 종료되므로 pop
이 이루어진다.
WEB API에서 비동기 작업들이 실행된 후, 호출되는 Callback함수들이
기다리는 공간
이다.
즉, 비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역이다.
여기서 Web API는 DOM 이벤트, AJAX, setTimeout등의 비동기 작업들을 수행할 수 있도록 지원하는 api이다.
또한, Task Queue는 하나의 큐가 아니라. Microtask Queue, Animation Frames 등 여러개의 큐로 이루어져 있다.
간단한 예제를 살펴보자. 👇
console.log(1);
setTimeout(function wait(){
console.log(2);
},3000);
console.log(3);
콘솔창에 출력한 결과이다. 어떤 과정으로 1->3->2
가 나오게 된 것일까?
- main()이 callStack에 올라간다.
- console.log(1)이 callStack push되면 1를 출력하고 pop으로 callStack 제거된다.
- setTimeout wait()가 callStack에 push된다.
- Web API가 이를 가져와서
비동기 작업
을 실행한다.- task queue로 wait()를 넘기고,
대기
한다.- callStack에 console.log(3)을 push하여 3을 출력하고 pop으로 callStack에서 제거된다.
main()
이 호출 stack에서pop
된다.- callStack이 비었으므로, task queue에서 대기 중이었던 wait()를 callStack으로 push하여 2를 출력하고 pop으로 callStack에서 제거된다.
그렇다면, setTimeout을 똑같은 초를 가지고 여러 번 호출하면 어떻게 될까?
다음 예제는 2초 후에 콘솔 창에 2를 출력하는 setTimeout()을 4번 사용한 코드이다.👇
setTimeout(function wait(){
console.log(2);
},2000);
setTimeout(function wait(){
console.log(2);
},2000);
setTimeout(function wait(){
console.log(2);
},2000);
setTimeout(function wait(){
console.log(2);
},2000);
각각에 함수에 대해서 callStack -> Web API로 이동하고, 차례대로 callBack queue에 들어가게 된다.
이 때, 가장 먼저 들어온 wait함수부터 callStack에 push -> 실행 -> pop
의 과정을 반복하게 된다.
즉, 4개의 함수를 2초 후에 실행하도록 설정했지만, 모든 이벤트가 동시에
실행되는 것은 아니다.
싱글 스레드 프로그래밍 언어인 자바스크립트에서의 이벤트 루프에 대해 알아보았다.
동일한 시간을 가진 콜백함수의 경우에도 하나씩 일이 처리된다는 것을 잊지말고 기억하자.
📚 학습할 때, 참고한 자료 📚