[ 글의 목적: 브라우저에서 html 내부 javascript 로 작성된 코드가 실제로 실행되기 까지의 과정과 해당 원리를 기록 ]
javascript
의 태생 자체는 "이렇게나 많은 일을 시키려고" 만들어지지 않았다. 그래서 그 근본이 조금 약한 부분은 있다.오히려 매력적이다시대의 변화에 맞춰서 js는 정말 다양한 특성을 가지는, 명령형(imperative), 함수형(functional), 프로토타입 기반(prototype-based) 객체지향 프로그래밍 을 지원하는 멀티 패러다임 프로그래밍 언어이면서 인터프리터 언어(Interpreter language) 이다.
사실 더 정확하게는 "브라우저의 작동원리" 를 이해하는게 더 좋다. 그래서 이 글의 정확한 범주는 "브라우저가 v8 엔진과 함께 javascript를 실행시키는 원리" 가 맞다. (그리고 브라우저 전체의 원리는 다루지 않는다.)
여기서 "JS 엔진" 이 우리가 .HTML
이라는 static file
에 <script>
로 감싼 javascript 코드를 실행하는 영역이다.
일단 "렌더링 엔진" 은 HTML 문서를 한 줄씩 순차적으로 파싱하다가 "자바스크립트 파일을 로드하는 script 태그를 만나면 DOM 생성을 일시 중단" 한다. script 태그의 src에 정의된 자바스크립트 파일을 서버에 요청하여 응답받으면 자바스크립트 코드를 파싱하기 위해 자바스크립트 엔진에게 제어권을 넘긴다. 자바스크립트 엔진의 내부 원리를 좀 더 살펴보자.
V8
(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
bytecode
를 실행함으로써 진짜 소스코드가 실행된다. 그 중 자주 사용되는 코드는 TruboFan으로 보내진다. TruboFan은 이 코드를 Optimized Machine Code
로 컴파일해놓고 사용한다.
V8은 8기통 엔진을 의미한다고 한다. Ignition
은 엔진 시동시에 사용되는 점화기
이며
TurboFan
은 가열되는 부분(자주 사용되는 부분)을 식히는 Fan
을 의미한다. 역시 꿈보다는 해몽이다.
Memory Heap
& Call stack
2개의 파트가 있다.데이터를 임시 저장하는 곳으로, 함수나 변수, 함수를 실행할 때 사용하는 값들을 저장한다. heap 저장공간
을 떠올리면 된다.
해당 부분에서 이제 더 이상 사용되지 않는 변수나 데이터 덩어리를 비워주는 GC(Garbage Collection)
가 있다. MDN - JavaScript의 메모리 관리 글을 추천한다.
코드가 "실행"되면 순서를 기록해 놓고, 하나씩 순차적으로 꺼내서 실제로 실행할 수 있도록 도와주는 공간이다. 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라고 부른다.
js는 태생적으로 "싱글 스레드(일하는 사람이 혼자)" 이기 때문에 위 call stack에서 10초 걸리는 작업이 하나라도 있으면 모든 작업은 10초 +a 로 느려진다. 그래서 필요한게 Event Loop
와 Callback Queue
, 그리고 browser web APIs
이다.
한 사람이 어떻게 미친듯이 영혼을 갈아 넣은 최적화를 해야할까? 에 대한 얘기다 ㅎ
지금 부터는 동시성 개념이 들어가기 때문에 좀 더 복잡해진다. Asynchronous & Non-blocking & Callback 에 대한 이해도가 필요하다. 이 글을 먼저 보고 오는게 도움이 된다! 그리고 예제는 js 실행 시뮬레이션 - latentflip 과 함께 보자.
우선 Event Loop
& Callback Queue
& browser web APIs
들의 역할은 아래와 같다.
"이벤트 발생 시 호출되는" 콜백 함수들을 관리하여 Callback Queue
에 전달하고, Callback Queue
에 담겨있는 콜백 함수들을 Call Stack
에 넘겨준다.
이벤트 루프가 Callback Queue
에서 Call Stack
으로 콜백 함수를 넘겨주는 작업은 Call Stack
에 쌓여있는 함수가 없을때만 수행된다. 반복적으로 Call Stack이 비어있는지 확인 하는 것을 tick
이라고 한다.
web api에서 비동기 작업들이 실행된 후 호출되는 콜백함수들이 기다리는 공간이다.
Event Loop
가 정해준 순서대로 줄을 서있으며, FIFO(First In First Out)
방식을 따른다. (Callback Queue
는 실제로는 하나의 큐로 이루어있지 않다. Microtask Queue
, Animation Frames
과 같은 여러개의 큐로 이루어져 있다.)
Web api는 브라우저에서 자체 지원하는 api이다.
Web api는 Dom 이벤트, Ajax (XmlHttpRequest)
, setTimeout
등의 비동기 작업들을 수행할 수 있도록 api를 지원한다. 브라우저가 제공하는 web api는 MDN 전체 Web API 에서 확인 가능하다.
console.log
를 실행하는 코드, 그리고 대표적인 비동기 함수인 setTimeout 을 실행한다.$.on
이벤트 바인딩 하는게 call stack에 들어오고 바로 web apis로 간다. 그리고 console.log
와 setTimeout
이 차례로 들어온다. 그리고 바로 실행된다. setTimeout(function timeout() {
console.log("Click the button!");
}, 5000);
setTimeout
으로 인해 5초마다 실행되는 console.log
가 계속 call stack을 들락 날락한다. 이제 바인딩된 버튼을 눌러보자Event loop
는 Call Stack
비어있는지를 "주기적으로 확인" 하고 Callback Queue
에서 Callback function
을 가져와 Call Stack
에서 Javascript 코드가 실행될 수 있도록 돕는 역할을 한다. 다음 글은 js 기본 원리 & 컨셉과 v8엔진을 탑재한 node에 대해 기록해 보려고 한다. .html
을 해석해서 사람이 이해할 수 있게 하는 S/W "브라우저" 를 벗어나, 어떻게 독자적으로 실행되는지 살펴보자.
정리 넘 깔끔합니다 👍🏻