[JS] 자바스크립트 동작 원리

10041 단어 JavaScriptJavaScript

✍🏻 개요

모던 자바스크립트 Deep Dive로 자바스크립트를 공부하던 중, 과연 어떠한 원리로 자바스크립트가 웹에서 구동되는지 그 동작 원리가 궁금해졌다. 프론트엔드의 수많은 라이브러리와 프레임워크들이 자바스크립트를 베이스로 구성되어 있고, 거의 프론트엔드의 유일한 '언어'라고 생각되기 때문에 동작 원리를 숙지해 둔다면 앞으로 공부에 있어 이해하기가 훨씬 수월할 것 같았다. 그렇기 때문에 해당 게시글에서는 자바스크립트가 어떻게 해석되고 동작되는지 알아보고자 한다.

🚗 자바스크립트 엔진

자바스크립트를 실행하기 위해서는 자바스크립트 엔진이 필요하다. 자바스크립트 엔진은 자바스크립트 코드를 실행하는 프로그램 또는 인터프리터이다.

인터프리터

프로그래밍 언어의 소스 코드를 바로 실행하는 컴퓨터 프로그램 또는 환경을 말한다. 원시 코드를 기계어로 번역하는 컴파일러와 대비된다. 인터프리터는 다음의 과정 가운데 적어도 한 가지 기능을 가진 프로그램이다.

1. 소스 코드를 직접 실행한다.
2. 소스 코드를 효율적인 다른 중간 코드로 변환하고, 변환한 것을 바로 실행한다
3. 인터프리터 시스템의 일부인 컴파일러가 만든, 미리 컴파일된 저장 코드의 실행을 호출한다.

자바스크립트 엔진의 대표적인 예시로는 구글의 V8 엔진이 있다. 파이어폭스의 SpiderMonkey, 사파리의 Webkit 등이 있지만, 크롬에서 사용하는 V8이 가장 대중적으로 알려져 있다.

자바스크립트 엔진의 주요 구성요소로는 메모리 힙(Memory Heap)과 콜 스택(Call stack)이 있다.

  • 메모리 힙(Memory Heap) : 메모리 할당이 일어나는 곳

  • 콜 스택(Call Stack) : 호출 스택이 쌓이는 곳

메모리 힙이란, 변수 선언, 함수 저장, 호출 등의 작업이 발생하는 공간이다. 예를 들어, const num1 = 531; 라는 코드는 num1이라는 변수를 위해 메모리 공간을 할당하고, 그 공간에 531이라는 값을 할당하는 것이다. 이와 같은 동작이 일어나는 공간이 바로 메모리 힙이며, 그림으로 비유하면 각각 변수명, 함수명이 라벨로 붙어있는 박스들이 메모리, 그 박스들을 보관하는 창고가 'Memory Heap'인 것이다.

콜 스택은, 코드를 읽어내려가면서 수행할 작업들을 밑에서부터 하나씩 쌓고, 메모리 힙에서 작업 수행에 필요한 것들을 찾아서 작업을 수행하는 공간이다. 즉, 콜 스택이란 기본적으로 우리가 프로그램 상에서 어디에 있는지를 기록하는 자료구조를 말한다. 만약 우리가 특정 함수를 실행하게 되면, 해당 함수는 콜 스택의 가장 상단에 위치하게 된다. 그리고 함수 실행이 끝나게 되면, 해당 함수는 콜 스택에서 제거되게 된다.

자바스크립트는 기본적으로 싱글 스레드(Single Thread) 기반 언어이기 때문에, 호출 스택이 하나이다. 따라서 한 번에 하나의 작업만 처리할 수 있다.

다음 예제 코드를 통해 자세히 알아보자.

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

처음 엔진이 위의 코드를 실행하는 시점에는, 콜 스택은 비어있는 상태이다. 하지만 코드가 실행되면서 콜 스택은 아래와 같이 변하게 된다.

콜 스택의 각 단계를 스택 프레임(Stack Frame)이라고 한다. 만약 스택을 초과하게 된다면 어떻게 될까? 무한 루프를 도는 코드를 돌리면 Maximum call stack size 에러가 발생하게 된다.

function foo() {
  foo();
}

foo();

콜 스택은 정해진 스택 사이즈가 존재하고, 하나씩 쌓이기 때문에 정해진 용량을 초과하게 되면 에러가 발생하게 된다. 흔히들 이것을 Stack Overflow라고 말한다. 유명한 개발자 포럼 사이트의 이름도 이것에서 따온 것이다.

🏃🏻 런타임

앞서 설명한 것처럼 자바스크립트 엔진은 싱글 스레드 기반 언어이기 때문에, 다른 작업을 수행하려면 현재 수행중인 작업이 끝날때까지 기다려야 하는 문제점이 있었다. 또한, 많은 시간이 걸리는 작업을 수행하게 된다면 프로그램이 엄청나게 느려질 우려 역시 존재했다.

그렇기 때문에, 오랜 시간이 걸리는 작업들은 백그라운드에서 처리하고 간단하게 처리할 수 있는 작업들만 콜 스택에서 처리한다면 효율적으로 처리가 가능하였다. 이러한 과정들이 가능하게 해준 것이 바로 자바스크립트 런타임이다.

Web API

API

API는 Application Programming Interface의 약자로 응용 프로그램에서 사용할수 있도록, 운영체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만드는 인터페이스를 뜻한다.

주로 파일 제어, 창 제어, 화상 처리, 문자 제어 등 자신이 원하는 기능을 제어할 수 있도록 하는 인터페이스를 제공한다. 따라서 원하는 기능을 제어하는 API를 직접 만들 수도 있다.

Web API란, Ajax 요청, setTimeout(), 이벤트 핸들러의 등록과 같이 웹 브라우저에서 제공하는 기능들을 말한다. 개발자 도구에서 콘솔창에 window를 입력해보자.

엄청나게 방대한 양의 Web API가 존재함을 확인할 수 있다. 이러한 Web API들을 사용하여 백그라운드에서 비동기적으로 작업을 처리할 수 있고 , 이러한 작업이 끝나면 자바스크립트 엔진(콜 스택)에게 해당 작업이 끝났음을 알려주어 계속해서 작업을 수행해 나갈 수 있게 된다.

콜백 큐(Callback Queue)

setTimeout이나 setInterval과 같은 비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역이다.

이벤트 루프(Event Loop)

이벤트 루프는 콜 스택에 현재 실행 중인 실행 컨텍스트가 있는지, 그리고 콜백 큐에 대기 중인 함수(콜백 함수, 이벤트 핸들러 등)가 있는지 반복해서 확인한다. 만약 콜 스택이 비어 있고 태스크 큐에 대기 중인 함수가 있다면 이벤트 루프는 순차적(FIFO, First In First Out)으로 콜백 큐에 대기 중인 함수를 콜 스택으로 이동시킨다. 이때 콜 스택으로 이동한 함수는 실행된다.

다음 예제를 통해 자세히 알아보자.

function foo() {
  console.log("a");
}

function foo2() {
  console.log("b");
}

foo();
setTimeout(function () {
  console.log("c");
}, 2000);
foo2();

자바스크립트는 하나의 콜 스택만 사용하기 때문에, 콘솔 창에는 순서대로 a, c, b로 출력된다고 생각할 수 있다. 그러나 결과값은 a, b, c로 출력되게 된다. 왜 그런지 각 코드의 출력 순서를 통해 알아보자.

  1. 제일 먼저 foo()라는 함수가 Call Stack에 쌓이게 된다.

  2. 그다음 foo() 함수 안에 있는 console.log()가 Call Stack에 쌓이게 된다.

  3. 콘솔 창에 a를 출력한다.

  4. foo() 함수는 종료되었으니 Call Stack에서 빠지게 되고, setTimeOut()이 Call Stack으로 들어오게 된다.

  5. setTimeOut()은 비동기 함수이기 때문에, Web API에서 처리하도록 보낸다. 그리고 다음 함수인 foo2() 함수를 불러온다.

  6. foo2() 함수 안에 있는 console.log()가 콜 스택에 쌓이게 된다.

  7. 콘솔 창에 b를 출력한다.

  8. foo() 함수는 종료되었으니 콜 스택에서 빠지게 된다. 이제 들어올 함수가 없기 때문에, 그동안 Web API에서 2초동안 처리중이었던 비동기 함수인 setTimeOut()의 시간이 지난 후에는 setTimeOut()의 콜백 함수를 콜백 큐로 보내게 된다.

    여기서 헷갈리면 안되는 점은, Call Stack에 있던 foo2() 함수가 종료되고 나서야 Web API를 처리하는 게 아니고 setTimeOut()이 Web API로 넘어간 시점부터 처리되고 있는 것이다. 즉, 콜 스택에서는 foo2() 함수를 처리하고 있었고, Web API에서는 setTimeOut()을 2초 동안 돌리고 있었다는 것이다.

  9. 이벤트 루프는 콜백 큐에 있는 콜백 함수를 콜 스택으로 보내서 처리하기 위해 콜 스택이 비어있는지를 검사한다. 만약 콜 스택이 비어 있다면 콜백 큐에 있던 함수를 콜 스택으로 보내서 처리하게 된다.

  10. 콜 스택에 있던 console.log()를 콘솔에 출력하는 것으로 프로그램이 종료된다.

🚨 결론

자바스크립트는 싱글 스레드 방식으로 동작한다. 하지만 싱글 스레드 방식으로 동작하는 것은 브라우저가 아니라 브라우저에 내장된 자바스크립트 엔진일 뿐이고, 만약 모든 자바스크립트 코드가 자바스크립트 엔진에서 싱글 스레드 방식으로 동작한다면 자바스크립트는 비동기로 동작할 수 없으며 프로그램의 처리 속도와 무게가 효율적으로 진행되지 않을 것이다. 즉, 자바스크립트 엔진은 싱글 스레드로 동작하지만 브라우저는 멀티 스레드로 동작하며, 이는 Web API, Callback Queue, Event Loop에 의해 가능하다.

참고 사이트

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

좋은 웹페이지 즐겨찾기