[번역] JavaScript 함수 실행 이해하기 — Call Stack, Event Loop , Tasks & more

원문 : https://medium.com/@gaurav.pandvia/understanding-javascript-function-executions-tasks-event-loop-call-stack-more-part-1-5683dea1f5ec

웹 개발자나 프론트엔드 엔지니어는 오늘날 브라우저의 상호 작용 소스 역할 부터 컴퓨터 게임, 데스크탑 위젯, 크로스 플랫폼 모바일 앱 제작 또는 데이터베이스와 연결되는 서버 사이드까지 스크립트 언어로 유비쿼터스를 달성합니다. 그래서 JavaScript 내부에 대해 더 잘 알고 효과적으로 사용하는 것이 중요하며, 이것이 이번 기사의 내용입니다.

JavaScript 에코 시스템은 그 어느때보다 복잡해졌으며, 계속 더 커질 것입니다. 최신 웹 앱을 구축하는 데 필요한 툴링은 Webpack, Babel, ESLint, Mocha, Karma, Grunt 등으로 압도적입니다. 무엇을 사용해야 하며 어떤 툴을 사용하고 있나요? 오늘 웹 개발자들의 투쟁을 완벽하게 보여주는 웹 코믹을 발견했습니다 :)

이외에도 프레임워크나 라이브러리를 사용하기 전에 모든 JavaScript 개발자는 꼭 이 모든 것이 루트 수준에서 내부적으로 어떻게 수행되는지 기초를 알아야 합니다. 대부분의 JS 개발자는 크롬의 런타임 V8이라는 단어를 들었을 지 모르지만 그 의미를 모르거나, 어떻게 동작하는지 모를 수도 있습니다. 처음 개발자로 경력을 쌓은 첫 해에 처음으로 작업을 완료하는 것과 관련해서 이러한 멋진 용어에 대해 많이 알지 못했습니다. JavaScript가 어떻게 이 모든 일을 할 수 있는지에 대한 호기심을 만족시키지 못했습니다. 나는 구글을 깊이 파고 들기로 결심했고, 필립 로버츠(Philip Roberts)를 포함하여 몇 가지 좋은 블로그 게시물을 보았습니다. JSConf의 이벤트 루프에 대한 훌륭한 이야기였습니다. 그래서 학습한 내용을 요약하고 공유하기로 했습니다. 알아야 할 것이 많으므로 기사를 두 부분으로 나누었습니다. 첫 부분에서는 일반적으로 용어를 소개하고, 두 번째 부분에서는 모든 용어를 연결합니다.

JavaScript는 단일 스레드 단일 동시 언어이므로 한 번에 하나의 작업 또는 하나의 코드를 처리 할 수 있습니다. 단일 호출 스택이 있으며, 힙과 같은 다른 부분과 함께 대기열은 JavaScript Concurrency Model(V8 내부에서 구현 됨)을 구성합니다. 먼저 다음 각 용어를 살펴보겠습니다.

1.호출 스택 (Call Stack) : 기본적으로 프로그램의 어디에 있는지 함수 호출을 기록하는 데이터 구조입니다. execute 함수를 호출하면, 스택에 무언가를 푸시(push)하고 함수에서 돌아올 때 스택의 상단에서 튀어 나옵니다(pop off).

위의 파일을 실행할 때, 먼저 모든 실행이 시작되는 기본 함수를 찾습니다. 위의 내용은 console.log(bar(6)) 에 시작하여 스택으로 푸시되고 그 다음 프레임은 인수가 있는 함수 bar 가 푸시되고 차례로 함수 foo 푸시를 호출합니다. 이 함수는 다시 스택의 맨위로 밀리고 즉시 반환되므로 스택에서 튀어나옵니다. 마찬가지로 bar가 튀어 나와 마지막으로 출력을 인쇄하는 콘솔 문까지 튀어나옵니다. 이 모든 것이 한 번에 하나씩(in ms) 수행됩니다.

브라우저 콘솔에서 가끔 긴 빨간색 오류 스택 추적을 볼 수 있습니다. 기본적으로 호출 스택의 현재 상태와 함께 함수 안에서의 스택 처 위에서 아래로 실패한 위치를 나타냅니다. (아래 이미지 참조)

때때로 우리는 재귀적으로 함수를 여러 번 호출 할 때 무한 루프에 빠지고, 크롬 브라우저의 경우 스택 크기가 16,000 프레임 제한이 있어 초과한다면 Max Stack Error Reached 오류를 발생시킵니다.(아래 이미지)

2.힙 (Heap): 객체는 힙에 할당됩니다. 즉 대부분 구조화되지 않은 메모리 영역입니다. 변수 및 객체에 대한 모든 메모리 할당이 여기에서 발생합니다.

3.큐 (Queue): JavaScript 런타임에는 처리할 메시지와 실행할 콜백 함수의 목록인 메시지 큐가 포함됩니다. 스택의 크기가 충분하면 메시지가 대기열에서 꺼내어 관련 함수를 호출하여 초기 스택 프레임을 만드는 메시지로 처리됩니다. 스택이 다시 비워지면 메시지 처리가 종료됩니다. 기본적으로 콜백 함수가 제공되면 이러한 메시지는 외부 비동기 이벤트 (예: 마우스 클릭 또는 HTTP 요청에 대한 응답 수신)에 대한 응답으로 대기합니다. 예를 들어 사용자가 버튼을 클릭하고 콜백 기능이 제공되지 않는다면 메시지가 대기열에 포함되지 않습니다.

이벤트 루프 (Event Loop)

기본적으로 JS 코드의 성능을 평가할 때 스택의 함수로 인해 속도가 느리거나 빨라지므로 console.log()는 빠르지만 for 또는 while 과 같은 반복으로 수천, 수백만 번 수행된다면 느려지고, 스택을 차지하거나 차단(block) 됩니다. 이를 웹 페이지 속도 통계에서는 blocking script 라고 합니다.

네트워크 요청이나 이미지 요청은 느릴 수 있지만, 다행히 서버 요청은 비동기 기능인 AJAX를 통해 수행할 수 있습니다. 이런 네트워크 요청이 동기적으로 이루어졌다면 어떻게 될까요? 네트워크 요청은 기본적으로 다른 장비의 컴퓨터인 서버로 전송합니다. 이제 컴퓨터가 응답을 다시 보내는 속도가 느려질 수 있습니다. 그동안 CTA 버튼을 클릭하거나 다른 렌더링을 수행해야 되어야 하지만, 스택이 차단되어 아무 일도 일어나지 않습니다. Ruby 같은 다중 스레드 언어에서는 처리할 수 있지만, JavaScript와 같은 단일 스레드 언어에서는 스택 내의 함수가 값을 반환하지 않으면 아무것도 할 수 없으므로 웹 페이지가 끊어질 것입니다. 최종 사용자에게 유동적인 UI를 보여주고 싶다면 이 방법은 이상적이지 않습니다. 이걸 어떻게 처리할까요?

“Concurrency in JS— One Thing at a Time, except not Really, Async Callbacks”

가장 쉬운 해결책은 비동기 콜백을 사용하는 것입니다. 즉, 코드의 일부를 실행하고 나중에 실행할 콜백 (함수)를 제공합니다. 우리 모두 $.get(), setTimeout(), setInterval(), Promises, etc 등을 사용하는 AJAX 요청과 같은 비동기 콜백을 경험해야 합니다. Node는 모두 비동기 함수 실행에 관한 것 입니다. 이 비동기 콜백은 즉시 실행되지 않고 나중에 실행되기 때문에 console.log()와 같은 동기 함수, 수학 연산과 달리 스택 내에서 즉시 푸시할 수 없습니다. 도대체 어디로 가고 어떻게 처리할까요?

위 코드와 같이 JavaScript에서 네트워크 요청을 한다면:

  1. 요청 함수가 실행되어 onreadystatechange 이벤트의 익명 함수를 콜백으로 전달하여 나중에 응답을 사용할 수 있을 때 실행도록 합니다.

  2. "Script call done"이 콘솔에 바로 출력됩니다.

  3. 언젠가 응답이 되돌아오고 콜백이 실행되어 본문을 콘솔에 출력합니다.

응답에서 호출자를 분리하면 JavaScript 런타임이 다른 작업을 수행하는 동시에 비동기 작업이 완료되고 콜백이 실행되기를 기다릴 수 있습니다. 2 이것은 브라우저 API가 실행되고 API를 호출하는 곳 입니다. 이 API는 기본적으로 DOM 이벤트, http 요청, setTimeout 등과 같은 비동기 이벤트를 처리하기 위해 C++로 구현 된 브라우저에 의해 생성된 스레드입니다. (이를 알고 난 후 Angular 2에서, monkey는 이러한 API를 패치하여 런타임 변경을 감지를 수행하는 Zones가 사용됩니다. 이 시점에서 사진을 얻을 수 있습니다)

Browser Web APIs- threads created by browser implemented in C++ to handle async events like DOM events, http request, setTimeout, etc.

이제 이러한 WebAPI는 자체적으로 실행 코드를 스택에 넣을 수 없으며, 코드 중간에 임의로 표시됩니다. 위에서 이야기한 메시지 콜백 큐는 그 방법을 보여줍니다. 3 WebAPI는 실행이 완료되면 콜백을 이 대기열에 푸시합니다. 이제 이벤트 루프는 대기열에서 이 콜백을 실행하기 위해 스택이 비어있을 때 스택으로 밀어넣는 역할을 합니다 4 . 이벤트 루프의 기본 작업은 스택과 작업 대기열을 모두 보고 스택이 비어 있다면, 큐의 첫번 째를 스택에 푸시합니다. 각 메시지 또는 콜백은 다른 메시지가 처리되기 전에 완전히 처리됩니다.

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

웹 브라우저에서는 이벤트가 발생하고 이벤트 리스너가 추가될 때마다 메시지가 같이 추가됩니다. 리스너가 없으면 이벤트가 유실됩니다. 따라서 click 이벤트 핸들러가 있는 요소를 클릭하면 다른 이벤트와 마찬가지로 메시지가 추가됩니다. 이 콜백 함수 호출은 호출 스택의 초기 프레임 역할을 하며 JavaScript는 단일 스레드이기 때문에 스택의 모든 호출이 리턴될 때까지 추가 메시지 폴링 및 처리가 중단됩니다. 후속 (동기식) 함수 호출은 스택에 새 호출 프레임을 추가합니다.

다음 부분에서는 위의 절차에 대한 코드 실행의 시각적 애니메이션을 보여주고, task , micro-task와 같은 다양한 유형의 비동기 기능이 무엇이며, 대기열의 우선 순위가 가장 높은 것을 설명합니다. 또한 제로 지연과 같은 핵은 일부 기능을 수행하는데 사용됩니다.

여러분이 그것을 좋아하고 개선을 돕기 위해 소중한 의견을 주길 바랍니다.

Last updated