STUDY/Javascript

JavaScript 작동 방식: 엔진, 런타임 및 호출 스택

_JJ_ 2023. 3. 20. 16:21

이 글은 “How JavaScript works: an overview of the engine, the runtime, and the call stack” 를 번역한 글입니다.

 

 

 

Javascript와 실제로 어떻게 작동하는지 더 깊이 파고드는 것을 목표로 하는 게시물입니다.

 

Javascript의 빌딩 블록이 어떻게 함께 작동하는지 알면 더 나은 코드를 작성할 수 있고, 앱 또한 경쟁력을 유지하기 위해 강력하고 성능이 뛰어난 경량 Javascript 어플리케이션인 SessionStack을 구축할 때 사용하는 몇 가지 경험 법칙을 공유할 것입니다.

프로젝트가 Javascript에 너무 많이 의존하고 있다면, 이는 개발자가 소프트웨어를 구축하기 위해 언어와 생태계 내부에 대한 더 깊은 이해를 제공하는 모든 것을 활용해야 한다는 것을 의미합니다. Javascript를 매일 사용하지만 내부에서 어떤 일이 발생하는지 모르는 개발자가 많습니다.

 

 

자바스크립트 엔진

자바스크립트 엔진의 대표적인 예는 Google의 V8 엔진입니다. V8 엔진은 Chrome 및 Node.js 에서 사용됩니다. 아래는 엔진의 구조도를 간단히 나타낸 그림입니다.

엔진은 두 가지 주요 구성 요소로 구성됩니다.

  • Memory Heap : 메모리 할당이 일어나는 곳
  • Call Stack : 코드 실행에 따라 호출 스택이 쌓이는 곳

 

런타임

브라우저에는 거의 모든 Javascript 개발자가 사용한 API가 있습니다 (ex setTimeout) 그러나 이러한 API는 엔진에서 제공하는 것이 아닙니다. 그럼 그들은 어디에서 왔느냐? 사실 현실은 좀 더 복잡합니다.

DOM, AJAX, setTimeout 등 브라우저에서 제공하는 Web API 라는 것들이 있습니다. (실제로는 훨씬 더 많습니다)

 

그런 다음 인기있는 이벤트 루프와 콜백 큐가 있습니다.

 

호출 스택(Call Stack)

Javascript는 싱글 스레드 프로그래밍 언어이므로, 호출 스택이 하나입니다. 따라서 한 번에 한 가지만 처리할 수 있습니다.

호출 스택은 기본적으로 프로그램에서 우리가 있는 위치를 기록하는 데이터 구조입니다.

만약 함수를 실행하면, 호출 스택 맨 위에 놓습니다. 함수의 실행이 끝날 때, 호출 스택의 맨 위에서 제거됩니다. 이것이 스택이 하는 일입니다.

 

예를 들어 보겠습니다.

function multiply(x, y) {
    return x * y;
}

function printSquare(x) {
    var s = multiply(x, x);
    console.log(s);
}

printSquare(5);

처음 엔진이 이 코드를 실행하는 시점에는 호출 스택이 비어 있습니다. 이후 단계는 다음과 같습니다.

호출 스택의 각 단계를 스택 프레임(Stack Frame)이라고 합니다.

 

보통 예외가 발생했을 때 콘솔 로그 상에서 나타나는 스택 트레이스(Stack Trace)가 오류가 발생하기 까지의 스택 트레이스들로 구성됩니다. 에러가 났을 때의 호출 스택의 단계를 의미하는 거죠.

 

// foo.js

function foo() {
    throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
    foo();
}
function start() {
    bar();
}
start();

이것을 크롬에서 실행하면 ?

"스택 날리기" 는 호출 스택이 최대 크기가 되면 발생합니다. 특히 코드를 광범위하게 테스트하지 않고 재귀를 사용하는 경우 매우 쉽게 발생할 수 있습니다.

 

function foo() {
    foo();
}
foo();

엔진이 이 코드를 실행하면 'foo' 함수를 호출하는 것으로 시작합니다. 그러나 이 함수는 재귀적이며 종료 조건 없이 자신을 호출합니다.

따라서 실행의 모든 단계에서 동일한 함수가 계속해서 호출 스택에 추가됩니다. 아래 사진처럼요.

그러나 어떤 시점에서 함수 호출 수가 호출 스택의 실제 크기를 초과하면 브라우저는 다음과 같은 오류를 발생시켜 조치를 취합니다.

다중 스레드 환경에서 발생하는 복잡한 시나리오(ex 데드락*)를 처리할 필요가 없기 때문에, 싱글 스레드에서 코드를 실행하는 것은 매우 쉽습니다.

그러나 싱글 스레드에서 코드를 실행하는 것은 상당히 제한적입니다. 호출 스택이 하나인 자바스크립트에서 작업이 느려지면 어떻게 될까요?

 

 

동시성(Concurrency) 및 이벤트 루프(Event Loop)

함수를 호출하는데 호출 스택 처리에 엄청난 시간이 걸리면 어떻게 될까요? 예를 들어 브라우저에서 자바스크립트를 사용하여 복잡한 이미지 변환을 한다고 생각해보세요.

 

왜 이게 문제가 될까요? 문제는 호출 스택에 실행할 함수가 있는 동안은 브라우저가 다른 작업을 수행할 수 없다는 것입니다. 즉, 브라우저가 렌더링할 수 없고 다른 코드를 실행할 수 없으며 그냥 멈춰 있습니다. 그리고 매끄럽고 유동적인 UI 를 원한다면 문제가 됩니다.

 

문제는 또 있습니다. 브라우저가 호출 스택에서 너무 많은 작업을 처리하면 오랜 시간 응답하지 않을 수 있습니다. 그리고 대부분의 브라우저는 웹 페이지를 종료할 것인지 묻는 오류를 발생시켜 조치를 취합니다.

 

 

자, 이상적인 사용자 경험은 아니죠, 그렇죠?

 

그럼 UI를 차단하지 않고, 브라우저를 멈추게 만들지 않고, 무거운 코드를 실행하려면 어떻게 해야할까요? 해결책은 비동기식 콜백 입니다. 이에 대해서는 V8 엔진 내부 + 최적화된 코드를 작성하는 방법에 대한 5가지 팁 에서 자세히 설명합니다.

 

 

 

 

 

*데드락: 또는 교착 상태. 두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태

 

reference

https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf

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