The Javascript Runtime Environment

uoM·2021년 2월 13일
2

javascript의 웹상에서의 동작 방식을 이해하는데 있어 도움이 되는 내용을 전달하고자 한다.
다음 원본을 번역하여 정리한 내용으로 오역이 있다면 지적 바랍니다.
The Javascript Runtime Environment

이 글은 javascript가 웹상에서 동작하는 방식에 대한 설명이다. 이 글을 통해
우리는 크롬의 V8 엔진이 코드를 적용하는 방법, 이벤트 루프(event loop)가 어떻게 싱글 스레드(single thread)에 동기, 비동기적 코드를 정리해 실행하는 방법을 알 수 있다.
결과적으로, 좀 더 쉬운 예시를 통해 자바스크립트가 동작하는 방식을 알게 된다.

시작에 앞서

우리가 firFox, chrome, edge, safari등을 통해 웹사이트에 들어올 때, 각각의 javascript(JS) 동작 환경(runtime Environment)을 갖게 된다.
각각의 환경에서 프로그램을 빌드할 수 있는 웹 API가 있다.

AJAX, the DOM(document object Model) tree, and other API's are not part of Javasciprt, they are just objects with properties and metohds, provided by the browser and made available in the browser's JS Runtime Environment

AJAX, the DOM tree, web API는 자바스크립트(Javascript)의 영역이 아니다. 이것은 JS 동작 환경에서 사용할 수 있는 오브젝트이다. 브라우저가 이것을 속성이나 메서드로 제공한다.

자바스크립트 동작 환경은 자바스크립트 엔진으로 코드를 적용 시켜준다. 브라우저 마다의 자바스크립트엔진 버전을 가지고 있으며, 크롬은 그 엔진을 V8 JS Engine이라 한다. 우리는 chrome의 V8 JS Engine을 알아볼 것이다.

The V8 JS Engine

크롬에서 자바스크립트나 스크립트 코드를 받으면 웹페이지에 V8 JS Engine이 파싱을 하게 된다.
처음에는, 구문오류에 대한 분석을 한다.
만약 구문오류가 없다면 다음으로 해당 스크립트를 위에서부터 읽어 내려간다. 결과적으로 자바스크립트를 컴퓨터가 이해할 수 있는 기계어로 바꾸게 된다. 그러나 코드가 무엇을 하는지 이해하기 전에 파싱되는 환경이 어떠한지 이해하는게 필요하다.

자바스크립트 런타임 환경

자바스크립트 런타임 환경을 하나의 큰 컨테이너 처럼 생각해보자.
그 컨테이너 안에는 다른 작은 컨테이너들이 있다. JS engine은 코드를 파싱하면 코드를 나누어 각각의 컨테이너에 전달하게 된다.

메모리 힙 (the Heap)

첫 번째 컨테이너는 V8 JS Engine의 일부인 메모리 힙(memory Heap)이다.
V8 JS Engine에서는 코드의 변수 및 함수를 힙(heap)에 저장한다.

콜 스택(the stack)

두 번째 컨테이너는 콜 스택이다. V8 JS Engine의 일부로 동작 가능한 코드를 넣어두며, 예를 들면 함수 실행문이 있다. 이러한 코드는 스택에 들어간다.

스택에 함수가 들어오게 되면 JS engine은 바로 해당 함수를 해석하기 시작해서, 힙에 변수를 추가하거나, 새 함수 호출을 스택에 최상단에 추가, 또는 함수를 Web API라는 세 번째 컨테이너에 전달해주기도 한다.

함수가 어떤 값을 반환하거나, Web API 컨테이너로 보내졌을 때, 스택에서 빠져나오게 된다. 그리고 스택에 있는 다음 함수로 넘어간다. 만약 JS Engine 함수의 끝까지 읽고 별다른 반환값이 정해지지 않았다면 undfined를 반환하고 스택에서 빠져나가게 된다.
함수를 해석하고 스택에서 제거되는 과정에서 자바스크립트 동작 환경에서의 동기를 알 수 있다.
동기적이란 한 번에 한가지 일을 싱글 스레드에서 처리한다.

Note: 스택이란 LIFO(last in first Out)인 데이터 구조를 말한다. 위에 더 이상 처리할 함수가 없을 때까지 다른 함수에 오지 않게 되며, 위에 함수가 스택에서 빠져나가지 않으면 다음 함수를 엔진이 해석하는일은 없게 됩니다.

웹 API 컨테이너(The Web API Container)

웹 API는 스택(stack)에서 Web API 컨테이너로 보내진 event listener, HTTP/AJAX Request, 또는 timing functions등이 실행될 조건이 되기 전까지 보관하고 있습니다.
만약 '클릭'이라는 일이 일어나거나, HTTP 요청이 완료되어 데이터를 받아오게 된다던지, 혹은 타이머에서 설정한 시간이되던지 하게되는 상황이 온다면, 각상황에 액션이 실행될 조건이되어 'callback function'이 네 번째이자 마지막 컨테이너인 '콜백 큐(callback queue)'로 보내집니다.

콜백 큐(The Callback queue)

콜백 큐는 callback function을 순서대로 저장해 두게 된다. '스택'이 완전히 비워진 순간에 '스택'으로 callback function을 큐의 처음부터 순서대로 보낸다. 스택이 비워지면 다음 callback function을 보낸다.

Note: 큐는 FIFO(first in first out)의 데이터 구조이다. 스택은 마지막에 더하고 마지막 부터 꺼낸다면, 큐는 마지막에 번째에 데이터를 추가하고 처음부터 꺼낸다.

이벤트 루프(The Event Loop)

이벤트 루프는 자바스크립트 실행환경에서 '무언가'로 생각할 수 있다. 이것은 지속적으로 '스택'과 '큐'를 살펴본다. 만약에 '스택'이 비었다면 '큐'에 알려 다음 callback function을 전달하게한다. 잠시나마 큐나 스택이 비게 되더라도 이벤트 루프는 큐와 스택을 체크하는걸 멈추지 않고 계속하게 된다. 웹 API를 통해 실행이 준비된 callback function이 언제든지 '큐'에 추가될 수 있기 때문이다.

이를 통해 자바스크립트가 비동기적으로 돌아갈 수 있는지 알 수 있다.
이는 정확히 사실은 아니지만, 실제로 그렇게 돌아가는 것 처럼 보인다. 그러나 자바스크립트는 동기적언어로, 한번에 처리 할 수 있는 일이 스택의 가장 위에 있는 한가지 일뿐이다.
하지만, Web API를 통해 계속해서 콜백을 에 넣을 수 있어, 에 있는 콜백을 끊임없이 스택으로 전달하는 것도 가능하다. 이러한 일이 자바스크립트가 비동기적으로 작동하는것 처럼 보이게 한다.

블락킹 vs 논-블락킹 I/O(Blocking vs Non-Blocking I/O)

우리가 블록킹 I/O에 대해 설명할 때, 무한루프, 실행이 끝나지 않는 function등을 떠올린다.
만약 function이 절대 종료되지 않아 스택에서 빠져나갈 수 없게 된다면, 스택에 있는 다음 function을 블락킹(blocking) 하고 있다고 한다. 다른 가능성으로는 실행하는 기능이 너무나 복잡한 계산이나 순서로 되어있어, 실행하는데 해석하는데 매우 오랜 시간이 걸린다면 다음 기능을 블락킹하고 있다고 한다. 보통 이런 것들은 코드를 작성하는 당시에 알아야 하는 것들인데, 해당 언어에서의 잘못(프로그램언어 상에서 일어나는 문제)이기 보다는 잘못 쓰여진 코드에서 발생할 가능석이 높다.

HTTP Request는 '블락킹 i/o'가 될 수 있다.
외부의 사이트로 부터 데이터를 요청하고 요청한 사이트의 결과를 기다려야 한다고 했을 때, 만약 결과가 오지 않게 되면 작성한 코드가 더 이상 실행되지 않고 막혀있게 된다. 하지만, 자바스크립트는 실행 환경에서 이러한 문제를 해결해 두었다. HTTP 요청을 보냈을 때 요청하는 부분을 Web API 컨테이너로 보내고 스택에서 제거한다. Web API 컨테이너에서 HTTP 요청에 대한 응답을 기다리게 되고 스택에서는 나머지 기능에대한 처리를 진행한다. 때문에 HTTP 요청에 대한 결과가 오지 않았더라도 작성한 코드는 계속해서 실행되게 된다.
Non-Blocking은 이처럼 다른 실행에 의해 다른 실행이 막히지 않고 계속해서 진행되는 것을 의미한다.

쉬운 예시

많은 글에서 다음과 같은 JS Runtime environment 예시를 볼 수 있다.

setTimeout(function(){
  console.log('Hey, why am I last?');
}, 0);

function sayHi(){
  console.log('Hello');
}

function sayBye(){
  console.log('Goodbye');
}

sayHi();
sayBye();

만약 위의 코드를 웹콘솔에 붙여넣기 해서 실행하면 다음과 같은 결과를 얻을 수 있다.

  • 1번 hello,
  • 2번Goodbye,
  • 3번undefined,
  • 마지막으로 Hey, why am I last?

비록 setTimeout function을 0초 이후에 실행하도록 제일먼저 써주었지만, 결과는 가장 마지막에 실행 되었다. 각 코드를 따라가보며 왜 sayhi(), sayBye()보다 늦게 setTimeOut()이 출력되었는지 알아보자.

  1. JS Engine(V8)이 스크립트에 문법적인 오류가 있는지 확인한 후, 문제가 없다면 처음부터 끝까지 해석하기 시작한다.

  2. setTimeout스택의 가장 상단에 추가한다.

  3. 해당 function(setTimeout)을 해석하기 시작한다. 이 기능은 Web API에서 처리해야 함으로 Web API로 넘겨주고 스택에서 제거한다.

  4. setTimeout의 두 번째 인자로 0초가 할당 되었기 때문에 콜백 큐에 익명함수 형태로 다시 추가해준다. function () { console.log('Hey, why am I last?} 이벤트 루프가 스택이 비었는지 확인한다. 하지만 아직 비어있지 않다. 왜냐하면...

  5. &6. setTimeout function이 Web API로 보내졌을 때, V8 Engine은 두 개의 function 선언을 보았다. 그 두개의 function을 heap에 저장하고 function 호출 부분인 sayHi()를 스택에 추가한다.

  6. sayHi()console.log()를 호출하여, 스택의 가장 상단에 추가한다.

  7. JS engine function을 해석하여, hello라는 메시지를 콘솔에 출력하고 스택에서 꺼낸다.

  8. JS engine은 다시 sayHi()로 이동하고 브라켓이 끝난것을 보고 스택에서 해당 함수를 제거한다.

  9. -12. 제거하자마자 다음 함수sayBye()를 호출하여, 스택에 추가한다. 이 함수를 해석해 console.log()를 호출하여 스택에 다시 추가한다.
    메시지GoodBye를 반환하고 다시 console.log()스택에서 제거한다. 다시 sayBye()함수로 돌아가 브라켓이 끝나는것을 확인하고 스택에서 제거한다.

  10. 이벤트 루프가 스택이 비어있는것을 확인하고, 큐에 있는 익명함수를 스택에 추가한다.

  11. 익명함수 내부를 해석해 console.log()를 스택에 추가한다.

  12. console.log()가 실행되고 스택에서 제거된다.

  13. 익명함수가 스택에서 제거되고 프로그램이 종료된다.

PS - 해당 코드를 그대로 복사하여 콘솔에 붙이는 경우 undefined이라는 결과를 중간에 찾아 볼 수 있다.
이것은 각각 선언된 함수가 반환하는 값으로 콘솔에 로그를 찍는것으로 실행된 함수는 실행후 제거되는데, 반환하는 값이 선언되지 않는 함수가 종료될 때 undefined라는 값을 반환하고 스택에서 제거되게 되어있다.

한번 해보기 (Now you Try!)

JS engine의 코드실행을 시각적으로 확인할 수 있는 사이트가 여기에 있습니다. 여기
나와 있는 예시를 잘보고, 페이지를 다시 불러와 예시코드를 확인해보세요. 본질적인 이해가 될 때까지 반복해서 해보시면 도움이 될것 같습니다.

브라우저 vs Node.js

이 글에서 꼭 기억해야하는 것은 브라우저 환경에서의 JS Runtime environment라는 것입니다.
Node.js는 같은 V8 JS Engine으로 구동되지만 지금과는 완전히 다른 runtime Environment를 제공하게 됩니다. 이는 Dom Tree, AJAX, Web API등이 없음을 뜻할 수 있습니다. 그러나,만들고자 하는 프로그램에 필요에 따라 필요한 package를 설치해 필요한 환경을 만들 수 있습니다.

더 알아볼 것

브라우저는 단지 JS runtime environment만 제공하는 것이 아니라, HTML, CSS, 그래픽 렌더링등을 실행하고 있습니다. 어떻게 다른 기능들을 브라우저가 다루는지 알아볼 필요가 있습니다.

0개의 댓글