24년 9월 원티드 프리온보딩 FE챌린지 사전과제로 이 영상을 보게 되었다.
대충 알고 있었던 이벤트 루프와 자바스크립트의 작동방식에 대해 정확히 이해할 수 있는 아주 좋은 강연이라 생각한다.
이 강연을 다 보고 나면 아래 내용에 대해 이해할 수 있다.
"자바스크립트 라는 것은 실제로 어떻게 동작할까?"
"V8과 크롬의 런타임은 무엇인가?"
"자바스크립트는 싱글스레드 라고 했는데 콜백을 사용한다. 실제로 콜백은 어떻게 동작하는가?"
그래서 강연 내용을 아주 자세히 기록해보려고 한다. 개인적으로 추가한 개념 정리 같은 경우 blockquote 안에 기록하였다.
"a single-thread non-blocking asynchronous concurrent language."
- 메모리 힙(Memory Heap): 변수와 객체의 데이터(원시 자료형이 아닌 보다 큰 크기의 데이터)가 저장되는 메모리 할당이 일어나는 곳.
- 콜 스택(Call Stack): 코드가 실행되는 순서를 기억하고 있다.
자바스크립트 엔진 과 종류
자바스크립트 엔진마다 코드를 해석하고 실행하기 위한 내부 동작 및 최적화 방법을 다를 수 있으며, 이로 인해 브라우저 간의 성능 차이가 발생할 수 있다.
- V8 (Google Chrome, Node.js)
Google에서 개발한 자바스크립트 엔진으로, Chrome 브라우저와 Node.js에서 사용된다. 빠른 실행 성능을 제공한다.- 스파이더몽키(SpiderMonkey) (Mozilla Firefox)
최초의 자바스크립트 엔진. Mozilla Firefox에서 사용되는 엔진이다.- 웹킷(Webkit)(Apple Safari)
Apple이 Safari 브라우저 용으로 개발한 엔진. Apple의 모든 디바이스에서 사용된다.- 차크라(Chakra) (Internet Explorer, Microsoft Edge)
Microsoft에서 개발한 Internet Explorer와 Edge 브라우저에서 사용하는 엔진이다.- 라이노(Rhino)
Java로 작성된 자바스크립트 엔진. Mozilla 재단이 오픈 소스 소프트웨어로 관리하고 있다. Mozilla에서 하나 더 개발하고 있는 C++로 작성되고 모질라 파이어폭스에 사용되는 스파이더몽키 엔진과는 구별한다.
자바스크립트는 싱글스레드 언어다. (스레드: 한 가지의 작업)
= 이 작업의 순서를 정하고 처리할 수 있는 콜 스택이 하나다.
= 하나의 프로그램은 동시에 하나의 코드만 실행 할 수 있다는 뜻.
콜 스택은 코드가 실행되는 순서를 기억하고 있다.
FILO(선입후출, First In, Last Out) 구조: 함수를 실행 하려면 스택에 실행할 함수를 집어 넣게 되는데, 함수에서 return이 일어나면 스택의 가장 위쪽에서 해당 함수를 꺼내게 된다. 이게 콜 스택이 하는 일의 전부다.
스택(Stack)은 처음에 들어간 것이 가장 마지막에 나오도록 되어 있는 구조를 갖고 있다. 이러한 구조를 FILO(선입후출, First In, Last Out) 또는 LIFO(Last In, First Out) 이라고 한다. 요소의 삽입과 삭제가 자료구조의 한쪽 끝에서만 이루어지는 것이다.
<일반적인 코드 실행 순서>
위 코드를 실행하면 먼저 실행하면 실행되는 코드 자체를 말하는 main() 함수를 콜 스택에 집어 넣게 된다.
그리고 실행 순서에 따라 함수를 콜 스택에 하나씩 쌓고, 위에서 부터 하나씩 실행한다.
그럼 multifly 함수 부터 실행하게 되는데,
스택에서는 무언가를 return 할 때 스택 맨 위에 있는 것들을 꺼내게 된다(pop).
그래서 multifly 함수부터 제거된다.
square 함수에서 그 결과값을 return 하고, 꺼낸다.
마지막으로 printSquare 함수에서 그 결과값을 squared 변수에 담고, 그 값을 출력한다.
printSquare 함수에서 return 코드는 보이지 않지만, 암묵적으로 return 하게 된다.
그리고 main() 까지 꺼내고 끝.
코드를 짜다 보면 무한루프에 빠져 이런 에러가 난 적 있을 것 이다.
이게 문제인 이유는
코드가 브라우저에서 실행되기 때문이다.
즉, 브라우저가 느려진다는 것.
동기적으로 ajax 요청을 보내는 getSync()
라는 jquery 함수가 있다고 치자.
url로 요청하는 getSync()가 요청을 끝낼 때 까지 로딩이 걸릴 것이다. 브라우저는 네트워크 요청을 하고 끝날 때 까지 마냥 기다린다.
일정 시간이 지나 첫번째 네트워크 요청이 끝나고, 이어서 두번째, 세번째 요청이 들어간다.
네크워크 요청이 끝날 때 까지 브라우저가 움직이지 않는다. 아무것도 클릭할 수 없다.
그리고 요청이 다 끝나야 멈춰있는 동안 해야 할 행동을 한다. (한번에 와다다 콘솔이 출력된다.)
원인은 콜 스택에 뭔가 남아있으면 동기적으로 실행되는 네트워크 요청이 콜 스택을 블록킹 하여 브라우저는 다른 일을 할 수 없게 되기 때문이다.
좋은 UI를 만들기 위해선 콜 스택을 멈추게 해선 안된다.
이걸 비동기 콜백으로 해결할 수 있다.
실제로 확인해보고 싶다면 아래 코드를 크롬 개발자도구 콘솔에 실행해보자. 브라우저가 잠깐 멈췄다가 그 동안 내가 한 클릭, 드래그 등의 작업이 한번에 와다다 실행된다.
for (let i= 0 ; i< 10**7; i+=1){
queueMicrotask(() => {});
}
[비동기 처리 코드 예제]
아래 코드를 실행하면 이렇게 순서대로 출력된다.
실행 순서를 하나씩 살펴보자.
먼저 실행 코드 자체를 의미하는 main() 함수가 콜 스택에 쌓이고 실행된다.
console.log('hi')를 콜스택에 쌓고 실행한다. 완료되면 꺼낸다(pop).
setTimeout을 콜스택에 쌓는다. 5초 뒤에 스택에서 사라진다. => 왜 인지는 아래에서 설명할 것.
console.log('JSConfEU')를 콜스택에 쌓고 실행한다. 완료되면 꺼낸다.
5초가 지나면 setTimeout의 console.log('there')를 콜스택에 쌓고 실행한다. 완료되면 꺼낸다.
이제 아까 위의 예제에서 setTimeout이 5초 뒤에 콜 스택이 사라지는 원리를 자세히 살펴보자.
[setTimeout이 스택에 들어왔을 때 실행순서]
1. setTimeout이 스택에 들어오고 실행하면, 브라우저는 타이머를 실행시키고 5초 카운트다운을 실행한다.
setTimeout 호출 자체는 완료되었기 때문에, 스택에서 setTimeout 함수를 제거한다.
(아직 5초 되지 않음) console.log(’JSC’)를 실행하고 제거된다. 이제 타이머만 남았다.
카운트다운이 끝나면(타이머 동작이 완료되면) setTimeout의 콜백 함수를 콜백큐(태스크큐)에 쌓는다.
이벤트 루프가 콜 스택이 비면 콜백 큐의 첫번째 작업을 콜 스택으로 옮기고, 실행한다. (여기까진 브라우저의 영역)
V8 엔진으로 돌아가서 콜백 작업인 console.log('there')을 실행하고, 완료되면 콜스택에서 제거된다.
이게 바로 setTimeout을 코드 마지막에 실행되는 원리이다. 즉, 스택이 비워질 때 까지 비동기 작업을 지연시키는 이유다.