글 작성자: bbangson
반응형

자바스크립트 동작 원리

 이벤트 루프를 공부하면서 자바스크립트 동작 원리 안에 이벤트 루프 개념이 포함된다는 것을 깨달았습니다. 그래서 이벤트 루프와 자바스크립트 동작원리를 같이 포스팅하고자 합니다. 

 

 자바스크립트는 단일 스레드 기반 언어입니다. 이 말은 JS 엔진(자바스크립트 엔진)이 단일 호출 스택을 사용한다는 의미입니다. 쉽게 말해서, 한 순간에 하나의 작업만을 처리할 수 있습니다.

 

 하지만 여러 웹 페이지의 자바스크립트 환경을 보면 많은 작업이 동시에 처리되고 있음을 알 수 있습니다. 대표적인 예로는, 애플 공식 사이트와 같이 애니메이션은 지속적으로 송출되면서 키보드 혹은 마우스 입력을 동시에 처리되는 경우가 있습니다. 

 

 자바스크립트는 단일 스레드 기반인 동기적인 언어라는 것을 알 수 있습니다. 그렇다면 어떻게 웹 브라우저에서 비동기적으로 동작하여 동시성을 지원받을 수 있는 것일까요? 

 

https://bbangson.tistory.com/87

 

[CS] 웹 브라우저는 어떻게 작동하는가?

웹 브라우저 작동 원리  주소창에 https://www.naver.com 혹은 https://www.youtube.com 등 다양한 URL을 검색하여 해당 웹 페이지에 접속한 경험이 있을 겁니다. 그렇다면 어떤 동작 원리로 우리가 입력한 웹

bbangson.tistory.com

 위 글과 같이 공부하면 이해하기가 더 수월할 것이라 생각합니다. 

 

 

 

 자바스크립트가 작동되는 웹 브라우저 환경

 

먼저 아래 사진을 눈에 익히고 가는 것이 좋습니다. 웹 브라우저 환경에서 자바스크립트가 작동되는 구조입니다.

브라우저 환경, 출처 = https://meetup.toast.com/posts/89

 자바스크립트를 웹 브라우저에서 작동하기 위해서는 JS 엔진, Web APIs, Callback Queue(Task Queue), Event Loop 영역이 필요합니다. 

 

 결론을 간단히 말하자면, JS 엔진에서는 단일 호출 스택(Call Stack)을 이용하여 동기적으로 요청을 처리하고 나머지 영역에서 웹 브라우저 환경 속에서의 자바스크립트가 비동기적으로 처리할 수 있게 지원해주는 역할을 합니다.  

 

 즉, 자바스크립트가 비동기적으로 동작하는 동시성은 JS 엔진을 구동하는 환경인 웹 브라우저나 Node.js에서 지원합니다.

 

 이 글에서는 웹 브라우저 환경을 위주로 작성하겠습니다.  

 

 

 

 자바스크립트 엔진

자바스크립트 엔진은 자바스크립트 코드를 실행하는 프로그램 또는 인터프리터입니다. 

자바스크립트 엔진의 대표적인 예는 Google V8이 있습니다.   

 

자바스크립트 엔진, 출처 = https://joshua1988.github.io/web-development/translation/javascript/how-js-works-inside-engine/

  자바스크립트 엔진은 Memory HeapCall Stack으로 구성되어 있습니다.

 

- Memory Heap

 메모리 할당이 일어나는 곳입니다. 프로그램에서 선언한 변수, 함수, 객체 모든 메모리 할당은 여기서 발생합니다. 

 

- Call Stack(호출 스택)

 코드가 실행될 때 함수의 호출을 스택 형식으로 저장하는 자료구조 입니다.   호출 스택은 스크립트에서 현재 어떤 함수가 동작하고 있는 지, 그 함수 내에서 어떤 함수가 동작하는 지, 다음에는 어떤 함수가 호출되어야 하는지 제어하는 지 등을 제어하고 기록합니다.

 

 호출 스택이 하나기 때문에 자바스크립트가 단일 쓰레드 기반 언어라고 불리는 이유입니다. 따라서 동기적으로 한 번에 한 작업만 처리할 수 있습니다. 스택 형식으로 쌓이기 때문에 가장 최근에 호출된 작업부터 차례로 실행합니다. 하나의 작업이 끝나면 pop()하고 그 다음 차례의 함수나 코드를 실행합니다. 작업은 차례대로 실행되므로 하나의 작업이 끝날 때까지 또 다른 작업을 실행하지 않습니다. 

 

 코드를 살펴보겠습니다.

function One(){
  Two();
  console.log("1");
}
function Two(){
  Three();
  console.log("2");
}
function Three(){
  console.log("3");
}

One();

/*
출력
3
2
1 
*/

 가장 먼저 one() 함수가 호출되고, 그 안의 two() 함수가 호출되고 마지막으로 three() 함수가 호출됩니다.

예시코드 콜스택 그림

 전역 컨텍스트는 첫 실행시 함수가 호출 되었을 때 생성되는 환경입니다. 가장 먼저 One()함수를 Call Stack에 push()합니다. 그리고 그 안에 있는 Two()함수를 push()하고 마지막으로 Three()함수를 push()합니다.

 

 각 함수의 실행이 완료되면 호출 스택에서 pop()됩니다.  Three() -> Two() -> One() -> 전역 컨텍스트 순서로 pop() 될 것입니다. pop() 됐다는 것은 함수가 실행 완료가 됐다는 의미이므로, 위의 예시 코드는  3 -> 2 -> 1 순으로 출력됩니다. 

 

 

 

 Web APIs

 Web APIs는 브라우저에서 자체 제공하는 API로, 비동기 작업등을 실행할 수 있는 DOM, Ajax, setTimeout 등이 있습니다.

 브라우저 환경 그림을 보면, Web APIs는 JS 엔진 밖에 존재하는 것을 알 수있습니다. JS 엔진까지가 자바스크립트를 동기적으로 작동시킨다면, Web APIs부터는 자바스크립트를 비동기적으로 작동시킬 수 있도록 지원하는 역할입니다.

 

 Call Stack에서 실행된 비동기 함수는 Web API를 통해 호출하고, Web API는 콜백 함수를 Callback Queue에 push합니다.

 

 비동기적으로 자바스크립트가 수행되는 구조는 아래 그림과 같습니다. 

 

1. 코드가 Call Stack에 쌓인 후, 비동기 함수는 Web API에게 위임합니다. 

2. Web API는 비동기 작업을 수행하고, 콜백 함수를 Callback Queue에 push합니다. 

3. 이벤트 루프는 Call Stack에 비어있을 때, Callback Queue에 대기하고 있던 콜백 함수를 콜스택으로 push합니다.

4. 콜스택에 쌓인 콜백 함수가 실행되고, 콜스택에서 pop 됩니다.

자바스크립트가 비동기적으로 실행되는 구조

 

 

 

Callback Queue

비동기적으로 실행된 콜백함수가 보관되는 영역으로, 선입선출(FIFO)로 출력됩니다.

 예를 들어, setTimeout에서 타어머가 완료 되고 실행되는 함수(첫번째 인자), addEventListener에서 click 이벤트가 발생했을 때 실행되는 함수(두번째 인자) 등이 저장됩니다. 

 

 Callback Queue에는 Task Queue, Microtask Queue, Animation Frames가 있습니다. Web API에서 비동기 작업들이 실행된 후 호출되는 콜백함수들이 기다리는 공간이며, 이벤트 루프가 정해준 순서대로 위치하게 됩니다. 

 

- Task Queue

setTimeout, setInterval, setImmediate, I/O, UI 렌더링 등의 콜백 함수가 저장됩니다.

 

- MicroTask Queue

ES6에 들어오면서 새로운 컨셉인 MicreTask Queue가 도입되었습니다. Promise, Object.observe 등의 콜백 함수가 저장됩니다.

 

- Animation Frames

requestAnimationFrame 콜백 함수가 저장됩니다.

 

* 위에서 Task Queue에 setTimeout이 있고, MicroTask Queue에는 Promise가 대표적으로 있다는 것을 알았습니다. 그렇다면 Event Loop에서 콜백 함수를 꺼내서 처리할 때 어떤 콜백 함수를 더 빨리 처리할까요? 정답은 MicroTask Queue입니다. 아래 코드를 통해 확인할 수 있습니다. 위의 세가지 중 우선 순위가 높은 순으로 나타내면 다음과 같습니다. 

 

Microtask Queue -> Animation Frames -> Task Queue

console.log('Call Stack!');
setTimeout(() => console.log('Task Queue!'), 0);
Promise.resolve().then(() => console.log('MicroTask Queue!'));

/*
출력
Call Stack!
MicroTask Queue!
Task Queue!
*/

 

 

 

 Event Loop

 이벤트 루프는 단일 호출 스택을 사용하는 자바 스크립트 엔진과 상호 연동하기 위해 사용하는 장치입니다.

 이벤트 루프를 통해서 동시성을 지원받을 수 있습니다. 이벤트 루프는 Call Stack과 Callback Queue를 감시하여, Call Stack이 비어있을 경우 Callback Queue에서 함수를 꺼내 Call Stack에 추가하는 기능을 합니다. 이와 같은 반복적인 행동을 틱(tick)이라고 부릅니다. 이벤트 루프가 Callback Queue에서 Call Stack으로 콜백 함수를 넘겨주는 작업은 콜스택에 쌓여있는 함수가 없을 때만 수행합니다. 

 

 자바스크립트는 코드 실행, 이벤트 수집과 처리, 큐에 놓인 하위 작업들을 비동기 적으로 담당하는 이벤트 루프에 기반한 동시성모델을 가진 언어입니다. C와 Java같은 언어와는 완전히 다른 모델입니다. 

 

 

 

 

 

 

 

 


 이벤트 루프를 공부하면서 자바스크립트의 동작 원리까지 같이 공부가 되었습니다.

이벤트 루프를 작성하는 것이 첫 목표였지만 공부하다 보니 이벤트 루프를 알기 전에 알아야할 것들이 있었고 이들을 알아갈수록 자바스크립트 원리와 이해가 더욱 잘 됐습니다. 맨 마지막 이벤트 루프 섹션이 내용이 제일 적은 것은 기분 탓인것 같습니다. 사실 저 섹션 전까지만 다 숙지해도 이벤트 루프는 자연스레 이해가 될 것 같다는 생각이듭니다 :) 

 

긴 글 읽어주셔서 감사합니다.

 

 

 

 

피드백 환영합니다. 

 

 

 

참고 링크

https://meetup.toast.com/posts/89

https://joshua1988.github.io/web-development/translation/javascript/how-js-works-inside-engine/

https://velog.io/@thms200/Event-Loop-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84

https://baeharam.netlify.app/posts/javascript/JS-Task%EC%99%80-Microtask%EC%9D%98-%EB%8F%99%EC%9E%91%EB%B0%A9%EC%8B%9D

반응형