자바스크립트는 어떻게 동작하나요?

Hailey Choi
7 min readDec 3, 2020

--

How JavaScript works: an overview of the engine, the runtime, and the call stack을 참고하여 자바스크립트의 동작원리를 자바스크립트 런타임, 콜 스택, 이벤트 루프를 중심으로 정리한 글입니다.

브라우저에서 자바스크립트는 어떻게 동작할까요? 브라우저는 자바스크립트를 해석하지 못하기 때문에 JS 해석기가 필요합니다. 이 해석기의 역할을 자바스크립트 엔진이 수행하는데요. 자바스크립트 엔진 중 가장 많이 알려진 것이 구글의 V8 엔진입니다. V8 엔진은 Chorme 과 Node.js 환경에서 사용되죠.

자바스크립트 엔진

자바스크립트 엔진

자바스크립트 엔진은 크게 두 가지로 구성됩니다.

  • 메모리 힙 (Memory Heap) : 메모리가 할당되는 곳
  • 콜스택 (Call Stack) : 코드가 실행됨에 따라 스택 프레임이 생기는 곳

하지만 자바스크립트가 자바스크립트 엔진만으로 돌아가지는 않습니다. 엔진을 비롯해서 자바스크립트가 실행되게 하는 환경이 존재하죠. 이 실행환경을 자바스크립트 런타임이라고 합니다.

자바스크립트 런타임

자바스크립트 런타임

자바스크립트 런타임은 자바스크립트 엔진을 비롯해서 브라우저의 Web API들, 이벤트 루프(Event Loop), 콜백 큐(Callback Queue)으로 구성됩니다. 이제 다시 자바스크립트 엔진으로 돌아가서 콜스택(Call Stack)부터 살펴볼까요?

콜 스택 (Call Stack)

자바스크립트는 싱글 쓰레드(Single Thread) 기반 언어입니다. 이 말은 하나의 콜스택을 가지고 있다는 말인데요. 따라서 한 번에 한 가지 일 밖에 처리하지 못합니다.

콜 스택은 기본적으로 프로그램 상에서 우리가 어디에 위치하고 있는지 기록하는 자료구조(Data Structure)입니다. 어떤 함수가 실행될 때, 그 함수는 자바스크립트 엔진의 콜스택 가장 위에 쌓입니다. 그리고 함수 내부에서 return이 발생하면 호출 스택에서 사라지게 됩니다.

예시를 통해 살펴보겠습니다.

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

function printNumber(x) {
var n = multiply(x, x);
console.log(n);
}

printNumber(5);

위의 함수가 실행되는 순간에는 콜스택이 비워져 있습니다. 이후에 아래의 그림으로 단계적으로 실행됩니다.

콜스택 실행 순서

콜스택에 쌓이는 각각의 entry를 스택 프레임(Stack Frame)이라고 합니다.

코드를 실행하다가 오류가 발생하면, 오류가 발생한 함수가 스택의 가장 위에 쌓여 있었다는 것을 알 수 있습니다.

콜스택이 수용할 수 있는 스택 프레임의 사이즈에는 한계가 있습니다. 만약 콜스택에 스택 프레임이 꽉 차게되면 어떻게 될까요? 브라우저가 스택을 날려버리는 스택 날려 버리기 (Blowing the stack)가 일어납니다.

break가 없는 재귀호출이 포함되어 있는 코드로 예를 들어보겠습니다.

함수안에서 자기 자신을 재참조하여 호출하는 것을 재귀호출(Recursive Call)이라고 합니다.

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

처음에 foo 함수가 실행이되면 break가 없기 때문에 계속해서 콜스택에 스택 프레임이 쌓이게 됩니다.

콜스택 Overflowing

결국 콜스택의 수용 한계를 넘게 되는데요. 이 때 브라우저는 스택을 날려버리고 아래와 같은 오류를 발생시킵니다.

Uncaught RangeError: Maximum call stack size exceeded

그렇다면 이러한 문제를 방지하기 위해서 매 함수의 실행이 끝나기를 기다려야 할까요? 우리는 다양한 기능을 가지면서 빠르게 동작하는 앱을 만들어야하는데 말이죠. 다행히 이러한 싱글 쓰레드 (Single Thread)의 한계를 보완해줄 방법이 있습니다. 바로 비동기 콜백(asynchronous callback)을 이용하는 것입니다.

자바스크립트의 비동기 콜백은 브라우저의 Web API들이 있고, ES6부터는 Promise 객체와 같은 자바스크립트의 내장 기능으로도 지원됩니다.

자바스크립트는 비동기 콜백을 통해서 한번에 여러가지 일을 처리할 수 있습니다. 자바스크립트가 실행되는 런타임에는 자바스크립트 엔진 뿐 만 아니라 이벤트 루프(Event Loop)도 있다는 것을 기억하실 텐테요. 이 이벤트 루프(Event Loop)가 자바스크립트가 동시에 여러가지 일을 하도록 도와주는 내장 메커니즘입니다.

이벤트 루프(Event Loop)

이벤트루프는 자바스크립트 앱 안에서 동시에 여러 가지 일이 실행되도록 핸들링 역할을 합니다.

지금 자바스크립트 앱 실행 중에 서버에서 필요한 데이터를 가지고 오는 비동기 요청이 있다고 가정해보겠습니다. 이 때 이벤트 루프는 브라우저에 이렇게 말할겁니다.

“브라우저야, 지금 이 콜백함수를 실행하면 데이터가 준비되는 시간만큼 너가 다른 일을 못하게 될테니, 네크워크 통신이 끝나고 데이터가 준비되면 그 때 내가 함수를 다시 호출(callback)할게.”

이 과정은 이벤트 루프가 콜스택(Call Stack)과 콜백 큐(Callback Queue)를 모니터링함으로써 이뤄집니다.

이벤트 루프와 콜백 큐

자바스크립트 코드 실행 중 이벤트를 만나면 콜백 큐(Callback Queue)에 순서대로 쌓입니다. 그리고 콜스택(Call Stack)이 비면, 콜백 큐(Callback Queue)에서 첫 번째 이벤트를 가지고 와서 밀어넣습니다. 이벤트 루프는 이 작업을 반복(loop)합니다.

여기서 이벤트는 콜백 함수 (function callback)이고 콜백 큐(Callback Queue)에서 콜스택(Call Stack)으로 이벤트를 밀어넣는 한 번의 작업을 틱(tick)이라고 합니다.

정리

자바스크립트가 돌아가는 실행환경을 자바스크립트 런타임이라고 합니다. 런타임은 자바스크립트 엔진(메모리 힙과 콜 스택), Web APIs, 이벤트 루프, 콜백 큐로 이뤄져 있는데요.

기본적으로 자바스크립트 코드가 실행되면 실행순서에 따라 스택 프레임이 엔진의 콜 스택(Call Stack)에 쌓이게 됩니다. 이벤트와 같은 비동기 콜백은 바로 콜 스택(Call Stack)에 쌓이는 것이 아니라, 콜백 큐(Callback Queue)에서 대기하게 되고요.

이벤트 루프가 콜 스택(Call Stack)과 콜백 큐(Callback Queue)를 모니터링 하면서, 콜 스택(Call Stack)이 비었을 때 콜백 큐(Callback Queue)에서 대기하고 있던 이벤트를 콜 스택(Call Stack)으로 미뤄넣습니다.

지금까지 자바스크립트의 동작원리를 간단하게 알아보았는데요. 자바스크립트 엔진에 대해서 더 자세히 알고 싶다면 Alexander Zlatkov의 How JavaScript works: inside the V8 engine + 5 tips on how to write optimized code를, 이벤트 루프의 작업 과정에 대해서 더 알고 싶다면 How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding with async/await를 참조하면 좋을 것 같습니다.

--

--