javascript - 기본 동작 원리와 v8 js 엔진

정현우·2024년 1월 1일
63
post-thumbnail

[ 글의 목적: 브라우저에서 html 내부 javascript 로 작성된 코드가 실제로 실행되기 까지의 과정과 해당 원리를 기록 ]

javascript 실행되기 까지

javascript 의 태생 자체는 "이렇게나 많은 일을 시키려고" 만들어지지 않았다. 그래서 그 근본이 조금 약한 부분은 있다. 오히려 매력적이다 시대의 변화에 맞춰서 js는 정말 다양한 특성을 가지는, 명령형(imperative), 함수형(functional), 프로토타입 기반(prototype-based) 객체지향 프로그래밍 을 지원하는 멀티 패러다임 프로그래밍 언어이면서 인터프리터 언어(Interpreter language) 이다.

1. javascript

사실 더 정확하게는 "브라우저의 작동원리" 를 이해하는게 더 좋다. 그래서 이 글의 정확한 범주는 "브라우저가 v8 엔진과 함께 javascript를 실행시키는 원리" 가 맞다. (그리고 브라우저 전체의 원리는 다루지 않는다.)

1) 엔진이 뭔데요

  • 웹브라우저는 하나의 커다란 해석기 S/W 이다. 웹브라우저 자체의 구성 요소는 아래와 같다.

  • 여기서 "JS 엔진" 이 우리가 .HTML 이라는 static file<script>로 감싼 javascript 코드를 실행하는 영역이다.

  • 일단 "렌더링 엔진" 은 HTML 문서를 한 줄씩 순차적으로 파싱하다가 "자바스크립트 파일을 로드하는 script 태그를 만나면 DOM 생성을 일시 중단" 한다. script 태그의 src에 정의된 자바스크립트 파일을 서버에 요청하여 응답받으면 자바스크립트 코드를 파싱하기 위해 자바스크립트 엔진에게 제어권을 넘긴다. 자바스크립트 엔진의 내부 원리를 좀 더 살펴보자.

2) 잠깐 살펴보는 V8

  • V8은 구글이 도입한 오픈소스 자바스크립트 엔진이다. C++로 작성되었으며 구글 크롬, 크로미움 웹 브라우저, NodeJS를 지원한다. 환경과 상호작용하고 프로그램을 실행하기 위한 바이트코드를 생성하는 역할을 담당 한다. V8과 다른 엔진의 가장 큰 차이점은 V8 엔진의 JIT(Just In Time) 컴파일러다.

  • 위 사진은 고수준의 V8 엔진 아키텍쳐다. 위 부분에서 Parser 부분 부터 조금 더 살펴보면 아래 그림과 같다.

파서(Parser)

  • (1) 렉시컬 분석 (Lexical Analysis) -> (2) 신택스 분석 (Syntax Analysis) -> (3) AST 의 흐름으로 Abstract Syntax Tree 를 만들어 낸다.

  • 사실 거의 모든 프로그래밍 언어는 AST를 이용해서 상위 수준의 코드 표현을 하위 수준의 표현으로 변환한다.

  • 해당 과정에 좀 더 심도 있는 이야기가 궁금하신 분은 다음 글에서 좋은 해답을 얻을 수 있을 것 같다. How JavaScript works: Parsing, Abstract Syntax Trees (ASTs) + 5 tips on how to minimize parse time

Ignition

  • 분석된 AST를 입력으로 사용하고 바이트 코드를 생성한다. 이에 따라 bytecode 를 실행함으로써 진짜 소스코드가 실행된다.

TruboFan

  • 그 중 자주 사용되는 코드는 TruboFan으로 보내진다. TruboFan은 이 코드를 Optimized Machine Code 로 컴파일해놓고 사용한다.

  • V8은 8기통 엔진을 의미한다고 한다. Ignition 은 엔진 시동시에 사용되는 점화기 이며
    TurboFan 은 가열되는 부분(자주 사용되는 부분)을 식히는 Fan 을 의미한다. 역시 꿈보다는 해몽이다.

3) 엔진의 내부 실행 원리

  • 일단 js 인터프리터 (js 코드 해석기) 가 코드를 읽고 실행하는 원리를 살펴보기 위해 실행되는 구조는 크게 아래 그림과 같다. "브라우저에서 자바스크립트 런타임 환경 실행 구성요소" 라고 보는게 더 정확한 표현이다.

js 엔진 내부만 보면 크게 Memory Heap & Call stack 2개의 파트가 있다.

Memory Heap

  • 데이터를 임시 저장하는 곳으로, 함수나 변수, 함수를 실행할 때 사용하는 값들을 저장한다. heap 저장공간 을 떠올리면 된다.

  • 해당 부분에서 이제 더 이상 사용되지 않는 변수나 데이터 덩어리를 비워주는 GC(Garbage Collection) 가 있다. MDN - JavaScript의 메모리 관리 글을 추천한다.

Call stack

  • 코드가 "실행"되면 순서를 기록해 놓고, 하나씩 순차적으로 꺼내서 실제로 실행할 수 있도록 도와주는 공간이다. stack 자료구조 형태이며 Last In, First Out ( LIFO ) 원칙을 따른다. (여기까진 여타 다른 언어에서도 동일한 구조를 많이볼 수 있다.)

  • call stack에 들어간 함수 형태는 아래 js 코드를 실행시킨 결과와 같다고 볼 수 있다.

function add(a, b) {
    return a + b;
}

function average(a, b) {
    return add(a, b) / 2;
}

console.log(average(10, 20));
  • 위 코드를 실행시키면 call stack에 순차적으로 console.log -> average -> add 가 쌓이고, 다 쌓인 뒤에는 이제 역순으로 add 결과 -> average 결과 -> console.log 결과로 이뤄진다.

  • 실제로 코드를 실행했을 때, 다음에 실행되어야 할 코드를 순서대로 기록을 하며, 순차적으로 코드를 실행할 수 있게 된다.

  • 이제 이 stack 이라는 자료구조에서 대표적으로 무한루프와 같이 저장공간이 가득 차서 넘치는 아래와 같은 현상 stack overflow라고 부른다.

4) js는 일하는 사람이 혼자다.

  • js는 태생적으로 "싱글 스레드(일하는 사람이 혼자)" 이기 때문에 위 call stack에서 10초 걸리는 작업이 하나라도 있으면 모든 작업은 10초 +a 로 느려진다. 그래서 필요한게 Event LoopCallback Queue, 그리고 browser web APIs 이다.

  • 한 사람이 어떻게 미친듯이 영혼을 갈아 넣은 최적화를 해야할까? 에 대한 얘기다 ㅎ

  • 지금 부터는 동시성 개념이 들어가기 때문에 좀 더 복잡해진다. Asynchronous & Non-blocking & Callback 에 대한 이해도가 필요하다. 이 글을 먼저 보고 오는게 도움이 된다! 그리고 예제는 js 실행 시뮬레이션 - latentflip 과 함께 보자.

  • 우선 Event Loop & Callback Queue & browser web APIs 들의 역할은 아래와 같다.

Event Loop

  • "이벤트 발생 시 호출되는" 콜백 함수들을 관리하여 Callback Queue 에 전달하고, Callback Queue 에 담겨있는 콜백 함수들을 Call Stack 에 넘겨준다.

  • 이벤트 루프가 Callback Queue 에서 Call Stack 으로 콜백 함수를 넘겨주는 작업은 Call Stack 에 쌓여있는 함수가 없을때만 수행된다. 반복적으로 Call Stack이 비어있는지 확인 하는 것을 tick 이라고 한다.

Callback Queue

  • web api에서 비동기 작업들이 실행된 후 호출되는 콜백함수들이 기다리는 공간이다.

  • Event Loop 가 정해준 순서대로 줄을 서있으며, FIFO(First In First Out) 방식을 따른다. (Callback Queue 는 실제로는 하나의 큐로 이루어있지 않다. Microtask Queue, Animation Frames 과 같은 여러개의 큐로 이루어져 있다.)

(Browser) Web APIs

  • Web api는 브라우저에서 자체 지원하는 api이다.

  • Web api는 Dom 이벤트, Ajax (XmlHttpRequest), setTimeout 등의 비동기 작업들을 수행할 수 있도록 api를 지원한다. 브라우저가 제공하는 web api는 MDN 전체 Web API 에서 확인 가능하다.

5) 상호작용 시뮬레이션

  • (1) 아래와 같은 코드를 실행한다고 생각해보자. html에는 button이 있고, 해당 버튼에 event를 바인딩하는 코드와 console.log 를 실행하는 코드, 그리고 대표적인 비동기 함수인 setTimeout 을 실행한다.

  • (2) 결과는 다음과 같다! $.on 이벤트 바인딩 하는게 call stack에 들어오고 바로 web apis로 간다. 그리고 console.logsetTimeout 이 차례로 들어온다. 그리고 바로 실행된다.

setTimeout(function timeout() {
    console.log("Click the button!");
}, 5000);
  • (3) 위 코드, setTimeout 으로 인해 5초마다 실행되는 console.log 가 계속 call stack을 들락 날락한다. 이제 바인딩된 버튼을 눌러보자

  • (4) 좀 혼란스럽지만, 버튼을 누를때마다 Callback Queue에 쌓이는 것, 그리고 쌓인게 다시 Event Loop에 의해서 call stack 으로 가서 실행되는 것, 그리고 2개의 다른 setTimeout 이 어떻게 실행되고 있는지 를 보면 된다.
  • 결과적으로 Event loopCall Stack 비어있는지를 "주기적으로 확인" 하고 Callback Queue 에서 Callback function 을 가져와 Call Stack에서 Javascript 코드가 실행될 수 있도록 돕는 역할을 한다.

다음 글은 js 기본 원리 & 컨셉과 v8엔진을 탑재한 node에 대해 기록해 보려고 한다. .html 을 해석해서 사람이 이해할 수 있게 하는 S/W "브라우저" 를 벗어나, 어떻게 독자적으로 실행되는지 살펴보자.


출처

profile
도메인 중심의 개발, 깊이의 가치를 이해하고 “문제 해결” 에 몰두하는 개발자가 되고싶습니다. 그러기 위해 항상 새로운 것에 도전하고 노력하는 개발자가 되고 싶습니다!

2개의 댓글

comment-user-thumbnail
2024년 1월 8일

정리 넘 깔끔합니다 👍🏻

1개의 답글