나는 백엔드 개발자이지만 JavaScript를 많이 사용하고 있다. 그렇다고 JavaScript의 작동 방식, 구조에 대해서는 모르고 있었다. 아는 거라곤 문법이랑 싱글스레드 언어이면서 인터프린터 언어..?(우선 컴파일은 아님!!!!!)이다 정도. 몰라도 여태 개발하는데 문제가 없었다고 생각했는데 큰 착각이라는 걸 이번 기회에 알게 됐다.
현재 참여 중인 프로젝트에서 Ajax로 인해 문제가 발생했다. 예를 들어 aAjax()가 먼저 실행되고 뒤이어 bAjax()가 실행되는데, a보다 b의 응답이 더 빨라서 b의 응답이 a에게 전달되는 문제가.. 로컬에서는 발생하지 않고 운영서버에서만 가끔 정말 가끔 발생해서 여태 모르고 있었다..! 차장님께서 먼저 발견하시고 같이 문제 해결 방법을 논의해 보자고 말씀하셨는데, 문제는 내가 속으로 ‘어떻게 그럴 수가 있지..?’ 문제의 원인이 짐작도 안 가고 그냥 문제 자체가 이해가 안 갔다. 그래서 문제 해결을 위해 JavaScript에 대해서 공부하게 됐다는 서사가 있다.
1. JavaScript의 동작구조
자바스크립트는 위에서 말했던 것처럼 싱글 스레드 언어이다. 그리고 하나의 스레드는 사진처럼 하나의 메모리 힙(Memory Heap) 영역과 콜 스택(Call Stack)을 가진다.
- 메모리 힙(Memory Heap) : 데이터를 만들 때(변수 선언 및 할당) 저장 및 사용되는 공간.
- 콜 스택(Call Stack) : 코드가 실행되면서, 함수가 쌓이는 공간으로 함수 실행 순서를 제어함.
콜스택과 메모리힙은 자바스크립트가 싱글 스레드이기 때문에 하나씩만 존재하고 이 말의 뜻은 한 번에 하나의 일만 처리할 수 있다는 뜻이다.

여기까지 읽었다면 당연히 의문이 들어야 한다.. 싱글 스레드인데 어떻게 비동기, 논블로킹 작업들을 지원하는가!!
이 부분이 바로 내가 여태 착각하고 있었던 점이다. 비동기와 같은 동시성 작업들을 JavaScript에서 지원하는 줄 알았더니 그게 아니라 JavaScript 엔진을 구동하는 런타임 환경에서 담당하는 거였다!! 우선 이건 이어서 3번에서 계속하고.
- 자바스크립트 엔진 : 자바스크립트 코드를 해석하고 실행하는 인터프리터 ex) V8, Chakra
- 자바스크립트 런타임 : 자바스크립트가 실행되는 환경. ex) 브라우저, Node.js
솔직히 엔진과 런타임이 아예 다르다는 걸 이론적으로는 알겠지만 체감? 하지는 못하겠다.. 나중에 더 자세히 공부해 보는 걸로..!
2. JavaScript 동작
위 사진을 예시로 JavaScript 동작 순서?를 설명해 보도록 하겠다. (stack이 Last In First out인건 설명 안 함, 너무 당연해서)
먼저 hello 함수가 Call Stack에 쌓이게 된다.
그리고 실행되어 함수 안에 있는 console.log가 실행되어 문장이 출력되고 함수가 종료되어 Call Stack에서 종료된다.
그 다음 age 함수가 Call Stack에 쌓이게 된다.
마찬가지로 함수가 실행되고 문장이 출력된 후 함수가 종료되면 Call Stack에서 종료된다.
여기까지만 보면 굉장히 단순하고 쉽다고 생각이 든다. JavaScript만 보면 그냥 순차적으로 실행되는 생태계?를 가진 거 같다. 이제 앞에서 잠깐 언급했던 JavaScript가 싱글스레드 언어인데도 비동기와 같은 작업이 가능한 이유?를 알아보자. (공부를 시작하게 된 원인이자 존재조차 몰랐던 존재 등장 예정)
3. 런타임 모델
위 사진이 JavaScript 엔진(좌측)과 외부의 런타임 환경(이벤트루프 + 우측)이 조합된 일반적인 구조이다.
JavaScript 언어에서는 멀티 쓰레딩 작업을 할 수 없지만, JavaScript가 작동하고 있는 브라우저에서는 여러 스레드를 사용할 수 있다. 그리고 여러 처리를 동시에 가능하게 하는 것이 바로 Event Loop이다.
정리하자면 JavaScript 엔진이 단일 Call Stack을 사용하고 JavaScript가 실행되는 환경에서는 주로 여러 개의 스레드가 사용되며, 이들이 상호 연동하기 위해 사용하는 장치가 Event Loop인 것이다.
Web API는 브라우저에서 제공하는 API로 setTimeout, fetch가 있다. 위 그림에서는 Queue가 Task랑 Micro로 나눠져 있는데, 몇몇 개의 글에서는 이 둘을 딱히 나누지 않을 때도 있는 거 같아서 고민했는데.. 나누는 게 더 정확한 거 같아서 나눠봤다.
Task Queue와 Micro Queue에는 우선순위가 있고 MicroTask Queue가 더 높은 우선순위를 가지고 있다. 그렇다면 어떠한 것들이 큐에 들어가느냐가 쟁점인데. 본격적인 설명하기 전에 간단하게 말을 하자면 setTimeout은 Task Queue, Promise는 Micro Queue에 추가된다.
내 생각보다 공부한 걸 정리한다는 게 어렵고 글을 쓴다는 것도 어려워서.. 다음 글로 넘어가야겠다..
2월부터 출근이 세종으로 바뀌어서 출퇴근 왕복 3시간 이상이 걸려서 그 시간 동안 정리해서 올려야겠다..
현재 회사에서 사용하고 있는 프레임워크를 마이그레이션 하는 작업을 하고 있는데 스프링 부트 3.0, jdk 17로 버전을 up 하는 작업을 하고 있는데 쉽지 않다.. 작업을 하면서 맞이하는 오류, 새로운 사실들도 정리하고 싶은데 집만 오면 피곤해서 문제..