[JavaScript] 동작원리
이번에는 전반적인 브라우저의 동작원리부터 크롬 브라우저의 V8엔진을 기준으로 자바스크립트의 동작 원리를 조금 상세히 알아보자.
해당 노트는 Alexander Zlatkov라는 개발자가 작성한 블로그를 참조하였다.
https://blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e
브라우저 동작원리
우선 브라우저의 전반적인 이해를 돕기위해 HTML과 CSS, JavaScript에 대한 대략적인 브라우저 동작을 알아보자.
(구체적인 브라우저의 동작 원리는 => https://d2.naver.com/helloworld/59361)
렌더링 엔진
- 브라우저에서 유저 인터페이스를 통해 사용자가 요청한 내용을 서버로부터 받아 브라우저 화면에 표시하는 역할을 한다.
 - 문서의 내용은 보통 8KB 단위로 전송받는다.
 - HTML 및 XML 문서와 이미지를 표시할 수 있다. 물론 플러그인이나 브라우저 확장 기능을 이용해 PDF와 같은 다른 유형도 표시할 수 있다.
 - 종류 - 사파리, 크롬은 애플의 웹킷 엔진 / 파이어폭스는 모질라의 게코 엔진
 - 동작절차
![javascript 동작절차 alt text]()
- 서버로부터 문서의 내용을 받는대로 0~5의 절차를 점진적으로 진행한다.
 - HTML 문서를 파싱하고 “콘텐츠 트리” 내부에서 태그를 DOM API를 통하여 DOM 노드(객체)로 변환한다.
 - 외부 CSS 파일과 함께 포함된 스타일 요소도 파싱한다. 이를 CSSOM 트리라고 하며 스타일이 없을 경우 브라우저가 정한 디폴트 스타일로 적용된다.
 - 1.의 일부 결과와 2.결과를 결합하여 렌더 트리를 생성. 렌더 트리는 색상이나 면적같은 시각적인 속성이 있는 사각형을 포함하는데, 정해진 순서대로 화면에 표시된다.
 - 렌더 트리(노드)를 정확한 위치에 배치시킨다.
 - 브라우저를 구성하는 UI 백엔드가 화면에 그리기 과정을 한다. 렌더 트리를 탐색하면서 특정 메모리 공간에 RGB값을 채운다.
 
 
일련의 과정들이 점진적으로 진행된다는 것을 아는 것이 중요하다.
렌더링 엔진은 좀 더 나은 사용자 경험을 위해 가능하면 빠르게 내용을 표시하기 위해 모든 HTML을 파싱할 때까지 기다리지 않고 배치와 그리기 과정을 시작한다.
네트워크로부터 나머지 내용이 전송되기를 기다리는 동시에 받은 내용의 일부를 먼저 화면에 표시하는 것이다.
자바스크립트 엔진
- 렌더링 엔진에서 HTML문서를 파싱하는 파서가 < script > 태그를 만나면 즉시 스크립트가 실행되며 그동안의 문서의 파싱은 중단된다. 이후 제어는 자바스크립트 엔진으로 넘어간다.
 - 스크립트가 외부에 있는 경우, 우선 네트워크로부터 자원을 가져와야 하는데 이 또한 실시간으로 처리되고 자원을 받을 때까지 파싱은 중단된다.
 - HTML5부터는 스크립트를 비동기(asynchronous)로 처리하는 속성을 추가하여 파싱순서에 상관없이 로드된 시점에서 스크립트를 실행시킬 수 있다.
 - DOM이 모두 로드되지 않은 시점에서 스크립트가 실행된다면 노드접근의 코드가 존재할 경우 에러를 발생시킨다. 따라서 일반적으로 HTML내부에서 스크립트 영역은 < /body > 태그 바로 이전에 배치하는 것을 권장한다.
 
자바스크립트의 런타임 환경

위의 그림처럼 자바스크립트 런타임 환경은 자바스크립트 엔진 이외에도 자바스크립트에 관여하는 요소들이 많다.
우리가 자바스크립트로 작성하는 DOM, AJAX, TimeOut등 API는 엔진에서 제공하는 것이 아니라 브라우저에서 제공하는 Web API라고 한다.
그 외에도 이벤트 루프, 콜백큐도 존재한다.
자바스크립트 엔진
대부분의 자바스크립트 엔진은 다음 두가지 구성요소로 이루어져 있다.
Memory Heap
메모리 할당이 발생하는 위치로, 동적으로 생성된 객체(인스턴스)가 이곳에 할당된다.
Call Stack
자바스크립트는 싱글 쓰레드(single-threaded)로 동작한다. 다시 말하면 Stack이 하나라는 뜻이다. 따라서 한 번에 하나의 일만 할 수 있다.
Call Stack은 기본적으로 우리가 프로그램 상에서 어디에 위치하고 있는지를 기록하는 자료구조이다. 만약 함수를 실행하면(실행 커서가 함수 안에 있으면) 스택에 가장 위쪽에 위치한다.
함수의 실행이 끝나면, 해당 함수를 스택에서 제거(pop)한다.
다음을 보자
1 | function multiply(x, y) { | 
2 |     return x * y; | 
3 | } | 
4 | function printSquare(x) { | 
5 |     var s = multiply(x, x); | 
6 |     console.log(s); | 
7 | } | 
8 | printSquare(5); | 
엔진이 이 코드를 수행하면 Call Stack은 비어있는 상태이며, 이후의 단계는 다음과 같다.
Call Stack의 각각은 스택 프레임(Stack Frame)이라고 부른다.
또한, 이것이 예외가 발생했을 때 스택트레이스가 만들어지는 방식이다.스택트레이스는 기본적으로 예외가 발생했을 때 Call Stack의 상태이다.
1 | function foo() { | 
2 |     throw new Error('SessionStack will help you resolve crashes :)'); | 
3 | } | 
4 | function bar() { | 
5 |     foo(); | 
6 | } | 
7 | function start() { | 
8 |     bar(); | 
9 | } | 
10 | start(); | 
위의 코드를 크롬에서 실행하면 다음과 같은 스택트레이스가 생성된다.
Call Stack이 최대 크기에 도달했을 때는 스택날림(Blowing the stack)현상이 발생한다.
1 | function foo() { | 
2 |     foo(); | 
3 | } | 
4 | foo(); | 
위의 코드를 실행하면 다음과 같은 현상이 발생한다.

싱글 쓰레드로 코딩하는 것은 멀티쓰레드 환경에서 대처해야 하는 상황을 신경쓰지 않아도 된다는 장점이 있지만, 제한점도 있다.
Event Loop와 Event Queue
자바스크립트의 런타임 환경의 이벤트 큐는 처리할 메세지 목록과 실행할 콜백 함수들의 리스트이다.
버튼 클릭 같은 이벤트나 DOM 이벤트, http 요청, setTimeout 같은 비동기 함수는 Web API를 호출하며 Web API는 콜백 함수를 콜백 큐에 밀어 넣는다.
이벤트 큐는 대기하다가 스택이 비는 시점에 이벤트 루프를 돌려 해당 콜백 함수를 스택에 넣는다.
이벤트 루프의 기본 역할은 큐와 스택 두 부분을 지켜보고 있다가 스택이 비는 시점에 큐의 콜백을 실행시켜 주는 것이다.
웹 브라우저에서는 이벤트가 발생할 때마다 메세지가 추가되고 이벤트 리스너가 첨부된다.
콜백 함수의 호출은 호출 스택의 초기 프레임으로 사용되며 자바스크립트가 싱글 스레드 이므로 스택에 대한 모든 호출이 반환될 때까지 처리가 중지 된다.
동기식 함수 호출은 이와 반대로 새 호출 프레임을 스택에 추가한다.
만약 콜스택 내에 수행시간이 긴 함수가 있으면 어떤 일이 벌어질까?
콜스택에 수행할 함수가 있으면 브라우저는 사실 아무것도 할 수 없다는 게 문제가 된다.
브라우저는 렌더링을 할 수도 없고 다른 스크립트 코드를 수행할 수도 없고 그야말로 이도저도 못하는 상황이 되어버린다.
더 나아가 브라우저가 콜스택 내의 많은 작업을 수행하면서 꽤 긴 시간 동안 응답이 없을 수 있다. 그러면 대부분의 브라우저에서 에러를 일으키고 사용자에게 해당 페이지를 닫을지 물어보기도 한다.
그러면 UI를 막지 않고 브라우저가 응답없음 상태에 빠지게 하지 않으면서도 무거운 코드를 수행하려면 어떻게 해야 할까?
바로 해결책은 비동기 콜백(asynchronous callback)이다. 비동기 콜백은 즉시 호출 스택에 쌓이지 않고 Event Queue에서 기다렸다가 호출 스택이 비어있는 시점에 실행된다.
비동기 처리에 대하여 추후 자세히 작성하도록 하겠다.
![]()


