원문 : 🚀⚙️ JavaScript Visualized: the JavaScript Engine
기계는 어떻게 우리가 작성한 코드를 이해할 수 있을까요? 자바스크립트 개발자로서 우리는 보통 "컴파일러"를 직접 다룰 필요는 없습니다. 하지만 자바스크립트 엔진의 기본적인 내용을 알고, 엔진이 어떻게 인간 친화적인 코드를 다루는지, 그리고 그 코드들을 기계가 이해할 수 있는 무언가로 바꾸는 것을 아는 것은 분명 좋은 일입니다.
Note : 이 게시물은 주로 Node.js, 크로미움 기반 브라우저에서 사용하는 V8 엔진을 기반으로 합니다.
HTML 파서는 source가 있는 script
태그를 만납니다. 이 소스의 코드는 네트워크, 캐시 혹은 설치된 서비스 워커로부터 로드됩니다. 응답은 byte stream에 요청한 스크립트 이며, byte stream decoder가 처리합니다. 바이트 스트림 디코더는 다운로드중인 바이트 스트림을 디코딩합니다.
바이트 스트림 디코더는 디코딩된 바이트 스트림으로부터 토큰을 생성합니다. 예를 들어
0066은 f로 디코딩 하고,
0075 -> u,
006e -> n,
0063 -> c,
0074 -> t,
0069 -> i,
006f -> o,
006e -> n 로 디코딩 하고 그 뒤에 공백을 붙입니다. function
을 적은 것 같군요! 이것은 자바스크립트의 예약어이고, 토큰이 생성되어 파서로 보내집니다. (gif에는 없지만 "pre-parser"에 대해서는 나중에 설명하겠습니다) 나머지 바이트 스트림도 마찬가지로 진행됩니다.
엔진은 두 개의 파서(pre-parser, parser)를 사용합니다. 웹 사이트를 로드하는 시간을 줄이기 위해, 엔진은 당장 필요하지 않은 코드를 파싱하지 않고 회피합니다. 프리 파서는 나중에 나중에 사용될 코드를 다루고, 파서는 당장 필요한 코드를 파싱합니다. 사용자가 버튼을 클릭한 후에만 호출되는 특정 함수의 경우, 웹사이트를 로드하기 위해 즉시 컴파일할 필요는 없습니다.
사용자가 버튼을 클릭하고, 해당 코드가 요청되면 그 때 해당 코드는 파서로 전달됩니다.
파서는 바이트 스트림 디코더로부터 전달받은 토큰을 기반으로 노드를 생성하고, 이 노드를 사용해서 Abstract Syntax Tree(AST)를 생성합니다.
다음은 interpreter의 차례입니다!. 인터프리터는 AST를 지나가며 AST에 포함된 정보를 기반으로 byte code를 생성합니다. 바이트 코드가 완전히 생성되면 AST가 삭제되어 메모리 공간에서 정리됩니다. 마침내, 우리는 기계가 사용할 수 있는 무언가를 가지고 있습니다! 🎉
바이트 코드는 빠르지만, 더 빨라질 수 있습니다. 이 바이트 코드가 실행되면서 정보가 생성됩니다. 특정 동작이 자주 발생하는지, 어떤 데이터 유형이 사용되었는지 탐지할 수 있습니다. 아마 함수를 수십번 호출한 적이 있을 겁니다. 이제 함수를 최적화하여 더 빨리 실행할 수 있습니다! 🏃
바이크 코드는 생성된 타입 피드백과 함께 최적화 컴파일러에 전달됩니다. 최적화 컴파일러는 이 두 가지를 받아 고도로 최적화된 기계어를 생성합니다.
자바스크립트는 동적 언어이기에 데이터의 타입이 지속적으로 변경될 수 있습니다. 자바스크립트 엔진이 매번 특정 값을 가지는 데이터 타입을 확인해야한다면, 매우 느릴겁니다
코드 해석에 걸리는 시간을 줄이기 위해, 최적화된 기계어는 바이트 코드를 실행하는 동안 엔진이 이전에 보았던 케이스만 처리합니다. 만약 같은 데이터 타입을 반복적으로 반환하는 특정 코드를 반복적으로 실행한다면, 최적화된 기계어를 재사용해서 속도를 높일 수 있습니다.
하지만 자바스크립트는 동적 언어이기 때문에 동일한 코드 조각이 갑자기 다른 타입의 데이터를 반환할 수 있습니다. 이 경우, 기계어는 최적화되지 않고 생성된 바이트 코드를 해석하는 엔진으로 돌아갑니다.
특정 함수가 100번 호출되면서 항상 같은 값을 반환한다고 가정해보세요. 101번째 호출에도 같은 값을 반환할거라 예상할 수 있습니다.
다음과 같은 함수 sum이 있다고 가정해 보겠습니다. 이 함수는 항상 숫자값을 인자로 불러왔습니다.
이 함수는 숫자 3
을 반환합니다! 다음에도 두 개의 숫자를 인자로 호출될 것이라 가정할 수 있습니다. 이 경우, 동적 조회(타입이 변경되었는지)를 할 필요가 없고 최적화된 기계어를 재사용할 수 있습니다. 만약 가정이 틀렸다면 (인자가 숫자가 아니라면) 최적화 기계어 대신 원래 바이트 코드로 되돌아갑니다.
예를 들어 다음 호출에 숫자대신 문자를 전달합니다. 자바스크립트는 동적 언어이기 때문에 에러 없이 이 동작이 수행됩니다.
이는 숫자 2가 강제로 문자열로 적용되어 함수가 문자열 '12'를 반환함을 의미합니다. 해석된 바이트 코드를 다시 실행하고, 유형 피드백을 업데이트 합니다.