오늘은 자바스크립트의 핵심원리 세 번째편 콜스택과 이벤트 루프에 대하여 알아보자🥁👌
호출 스택과 이벤트 루프는 자바스크립트 코드 동작원리를 이해하는 데 필수라고 하니 꼭 알아두자!
자바스크립트는 기본적으로 싱글 스레드 프로그래밍 언어(Single threaded programming language)
이다.
이 말은 한 번에 하나의 작업만 할 수 있다는 뜻이다.
여기서 call stack
은 무엇일까?
호출스택(call stack)
은 프로그램 상에서 우리가 어떤 순서로 작업을 수행하는 지 기록하는 작업스케줄링과 관련된 자료 구조이다.
push
라고 한다. 또한 함수가 어떤 값을 리턴하거나 실행을 종료할 경우 그 함수를 스택 맨위에서부터 꺼내는데 이것을 pop
이라고 한다. 예제(1)
function first() {
second();
console.log('첫 번째');
}
function second() {
third();
console.log('두 번째');
}
function third() {
console.log('세 번째');
}
first();
third();
(first()
가 호출되었을 때)
호출스택
이라고 부른다. 예제(2)
어떤 순서로 스택이 되는지 한 번 생각해보자!
function multiply(a,b) {
return a*b;
}
function square(n) {
return multiply(n,n);
}
function printSquare(n) {
var squared = square(n);
console.log(squared);
}
printSquare(4)
[PUSH]
위 코드를 실행하면 일단 저 함수들을 감싸고 있는 main() 함수를 스택에 넣는다.
그리고 printSquare(4) 함수가 실행되므로 printSquare(4)를 스택에 넣는다.
printSquare() 함수 내부에서 square(n) 함수를 실행하므로 스택에 square(n)을 넣는다.
square(n) 함수 내부에서 실행되는 multiply(a, b)를 스택에 넣는다.
[POP]
드디어 처음으로 리턴이 되었다. multiply(a, b)가 값을 리턴하며 스택에서 나온다.
square(n)도 값을 리턴하고 나온다.
printSquare(n)에서 console.log(squared)를 스택에 push하고 실행한 후 다시 빠져나온다.
printSquare(n) 함수가 종료되었으므로 stack에서 나온다.
main() 함수가 종료되었으므로 스택에서 나온다.
자바스크립트는 이렇게 싱글 스레드 언어이기 때문에 함수를 실행하면 함수 호출이 순차적으로 쌓이고 스택의 맨 위에서부터 차례대로 한 번에 하나의 함수만 처리할 수 있다!
하지만 잠깐!
이렇게 다른 작업을 실행하기 위해서 이 전 작업이 완료될 때까지 기다려야만 하는 상황을 블로킹(blocking)이라고 한다
왜 이것이 문제가 되냐면 어떤 작업이 실행되어 동작하고 있는 동안에는 브라우저가 다른 일을 전혀 할 수 없으므로 잠시 먹통이 되기 때문이다. 만약 이렇게 브라우저가 블락되는 순간이 잦다면 사용자의 불만은 점점 커질 것이다.
이러한 점을 극복하기 위한 해결방안이 바로 Asynchronous Callbacks(비동기 콜백)
이다!
자바스크립트가 싱글 스레드 언어임에도 불구하고 우리가 웹 사이트에서 끊김없이 여러 작업을 동시에 할 수 있는 것은 바로 브라우저가 Web APIs 같은 것들을 제공하여 비동기 작업을 가능하게 해주기 때문이다.
동작원리
- 함수를 동기 호출하게 되면 call stack에 차곡차곡 쌓여 순차적으로 실행된다.
- 이 때 만약에 우리가 AJAX나 setTimeout 혹은 DOM event를 실행하면 자바스크립트 엔진은 call stack에서 Web APIs로 보내고 정해진 시간 혹은 이벤트가 발생한 순간에 순차적으로 callback queue에 적재한다.
- callback queue에 줄을 선 함수들은 call stack에 쌓여있던 것들이 모두 제거되어 깨끗해지면 차례대로 스택에 쌓여서 실행되게 된다.
큐(queue)
여기서 queue는 지난 블로그에서 다뤘지만 잠깐 언급하면 큐는 스택(Stack)과 같이 자료 구조의 일종이다. 한 쪽에서만 삽입과 삭제가 이루어졌던 스택과는 달리 한쪽에서는 삽입이 되고 다른 한쪽에서는 삭제 작업이 이루어지는 자료 구조이다.
가장 먼저 삽입된 자료가 가장 먼저 삭제되는 구조이므로 선입선출(FIFO: First In First Out)이라고도 부른다.
예제를 통해 이벤트 루프의 로직을 이해해보자
예제(1)
console.log(1);
setTimeout(function cb() {
console.log(2);
},5000);
console.log(3);
task queue
에 들어온다. 위 로직을 이 사이트에서 꼭 실행해보자
위의 경우 setTimeout이 5초 후에 실행하는 코드이기 때문에 순서가 1-3-2의 순서로 명확하지만 만약 setTimeout 함수를 0초 후에 실행하도록 코드를 변경하면 어떨까?
console.log(1);
setTimeout(function cb() {
console.log(2);
},0);
console.log(3);
이 경우에도 결과는 다르지 않다.
그 이유는
task queue
에 줄 서 있는 callback 함수들은 stack이 비어있을 때만 stack으로 이동할 수 있기 때문이다.
위 경우 setTimeout으로 설정한 cb함수는 web APIs로 이동하는 즉시 task queue로 이동하게 되는데, stack이 비어있지 않기 때문에 대기 상태로 있게되고 console.log(3)이 출력되고 스택이 비워지면 이동하여 console.log(2)가 실행된다.
이벤트루프는 호출 스택을 주시하고 있다가 호출 스택이 비워지면(전역 컨텍스트 main 실행이 종료되면) 태스크 큐에서 함수를 하나씩 호출 스택으로 밀어 올리는 역할을 한다.
참고사이트
1. https://www.zerocho.com
2. https://im-developer.tistory.com/113