Skip to content

JavaScript 이벤트 루프 이해하기

BbangYa edited this page May 20, 2019 · 3 revisions

이벤트 루프, 콜백 대기열, 동시성 모델 및 콜 스택이라는 용어에 대해 들어 본 적이 있지만
실제로 의미하는 것을 이해하지 못하는 사람이라면이 게시물을 참조하십시오.
그렇지만 숙련 된 개발자라면이 게시물을 통해 언어의 내부 작업을 이해하고보다 뛰어난 사용자 인터페이스를 작성할 수 있습니다.

언어로서의 JavaScript는 지난 10 년 동안 기하 급수적으로 성장하여 다양한 수준의 개발자 스택,
즉 프론트 엔드, 백엔드, 하이브리드 앱 등에 대한 도달 범위가 확대되었습니다.
이전에는 브라우저와 관련하여 자바 스크립트에 대해 이야기했던 그 날은 없었습니다.
인기와 수요 증가에도 불구하고 언어가 내부적으로 어떻게 작동하는지 이해하는 개발자는 거의 없습니다.
이 게시물은 JavaScript가 작동하는 방식과 이전에 사용했을 가능성이있는 언어와 비교했을 때
이상한 점을 명확히하고 강조하기위한 것입니다.

개요

C ++ 또는 Ruby와 달리, JavaScript는 단일 스레드 언어입니다.
즉, 한 번에 한 가지만 할 수 있으며 다른 일은 할 수 없다는 것을 의미합니다.
교착 상태와 같은 멀티 스레드 언어와 관련된 복잡성을 처리 할 필요가 없으므로 자체 이점이 있지만
브라우저가 모든 작업을 중지하기 때문에 이미지 조작이나 기타 무거운 프로세스와 같은 복잡한 작업을 수행 할 수 없음을 의미합니다.
그렇지 않으면 그 작업을 수행합니다.

JavaScript 런타임

크롬을 구동하는 JavaScript 엔진 인 V8에 대해 들어봤을 것입니다.
JS 엔진은 Heap과 Call Stack의 두 가지 주요 구성 요소로 구성됩니다.
힙은 모든 메모리 할당 (및 할당 취소)이 이루어지는 곳입니다.
호출 스택은 기본적으로 우리가 프로그램에있는 위치를 기록하는 데이터 구조입니다.
즉, 프로그램에 실행 컨텍스트가 있으면 스택에 푸시 컨텍스트가 발생하고 컨텍스트가 반환 될 때 컨텍스트가 Pop됩니다.
대부분의 시나리오에서 실행 컨텍스트는 함수 호출 일뿐입니다.
이를보다 명확하게 이해하기 위해 이 간단한 프로그램을 살펴보고 호출 스택 내에서 실행되는 방식을 시각화 해 봅니다.

function multiply(a, b) {
   return a*b;
}
function square(a) {
   const sq = multiply(a, a);
   
   console.log(sq);
}
square(3);

JS 엔진은 힙의 함수 선언에 대한 메모리를 초기화합니다.
11 번 라인에 도달하면 함수가 실행되고 JS 런타임은이를 호출 스택에 푸시합니다.
다음 단계는 각 단계에서 호출 스택의 상태를 보여줍니다.

호출 스택의 각 항목을 스택 프레임이라고합니다. 브라우저가 오류 발생시 스택 추적을 인쇄 할 때 이미 보셨을 것입니다.

function customError() {
   throw new Error("Print the stack trace from here!!");
}
function foo() {
   customError();
}
function bar() {
   foo();
}
bar();

파일의 이름이 main.js라고 가정하면 다음 스택 추적이 콘솔에 표시됩니다

무한 시간을 호출하는 재귀 함수가 있다고 가정 해 봅시다. 예를 들어 -

function bar() {
   bar();
}
bar();

실행시 호출 스택에 메모리가 부족해 지거나 범위를 벗어난 오류가 발생할 때까지 함수 막대가 각 호출의 호출 스택에 추가됩니다.
이것은 불명예스럽게 스택을 부르는 것으로 알려져 있습니다.

비동기식으로 이야기합시다.

비동기 코드를 실제로 파헤 치기 전에 호출 스택에 블로킹 또는 동기 코드가 미치는 영향을 살펴 보겠습니다.
예를 들어 아래의 jQuery 코드를 살펴 보자.

$.ajaxSetup({async:false});
const resposne1 = $.get('https://example.com/api/data1', (res) => {
   return res;
});
const resposne2 = $.get('https://example.com/api/data2', (res) => {
   return res;
});
const resposne3 = $.get('https://example.com/api/data3', (res) => {
   return res;
});
console.log(resposne1);
console.log(resposne2);
console.log(resposne3);

첫 번째 줄은 기본적으로 모든 ajax 요청을 동기식으로 설정합니다.
따라서 우리는 Ajax 요청의 응답을 기다리는 동안 호출 스택이 차단되고 해당 프로그램은 그 시간 동안 응답하지 않게됩니다.
브라우저에서 위 코드의 동일한 동작을 시뮬레이트하면 페이지의 다른 모든 요소가 차단 된 상태가됩니다.
즉, 차단 된 상태를 벗어날 때까지 상호 작용할 수 없습니다.
이것이 동기 네트워크 요청이나 계산 시간이 많이 소요되는 다른 작업을 수행하는 것은 나쁜 습관 인 이유입니다.

이에 대한 해결책은 비동기식 콜백입니다. 아마도 이미 프로그램에서 비동기 코드를 사용했을 것입니다.
setTimeout 또는 xhr 요청과 같은 API는 비동기입니다.
그러나 실제로 작동하는 방법을 탐색하기 전에 비동기 코드를 실행할 때 호출 스택이 어떻게 나타나는지 시각화 해 보겠습니다.

  • 위 이미지는 본문과 다르지만 본문 이미지의 문제로 다른 비슷한 이미지로 대체하였습니다.

실행시 프로그램은 setTimeout을 만나면 일정한 간격 후에 실행되도록 대기 한 다음 다음 줄로 이동합니다.
스택이 비어지면 (그리고 타임 아웃이 끝난 후) setTimeout 콜백이 마술처럼 스택에 푸시되고 실행됩니다.
다음 섹션에서 이러한 현상이 어떻게 발생하는지 살펴 보겠습니다.

동시성 모델과 이벤트 루프

JavaScript가 단일 스레드 인 경우 동시 프로세스를 실행할 수있는 방법을 생각할 수도 있습니다.
자바 스크립트 런타임은 한 번에 하나만 할 수 있지만 브라우저 자체는 런타임 이상입니다.
브라우저는 Web Apis, 콜백 대기열 및 이벤트 루프와 같은 다른 항목으로 구성됩니다.
웹 API는 호출 스택을 지우는 동안 요청할 수 있고 프로세스를 수행 할 수있는 스레드입니다.

이것이 전체 JavaScript 환경의 모습입니다.
노드 컨텍스트에서 위의 그림은 웹 API 대신 스레드, 파일 시스템 등의 C ++ API를 사용한다는 점을 제외하고는 동일하게 유지됩니다.
이전 섹션에서 실행 한 코드로 돌아가서 더 큰 그림에 어떻게 적용되는지 확인해 봅시다.

setTimeout, xhr 등의 Apis는 런타임에는 없지만 Web API에서는 제공됩니다.
setTimout 함수를 호출하면 콜백과 함께 타이머 함수가 등록됩니다. 타이머가 만료되면 콜백을 이벤트 대기열로 보냅니다.
이 콜백은 호출 스택이 비어있을 때 이벤트 루프에 의해 호출 스택에 푸시됩니다.

이벤트 루프에는 단일 작업이 있습니다.이 이벤트 루프는 콜 스택 및 콜백 대기열을 감시합니다.
호출 스택이 비어 있으면 큐의 첫 번째 이벤트를 가져 와서 효과적으로 실행하는 스택으로 푸시합니다.
이러한 반복을 이벤트 루프의 틱 (tick)이라고합니다. 각 이벤트는 단지 함수 콜백입니다.

setTimeout(...)에 대한 사항

비동기 코드와 처음 마주친 것 중 하나는 이것이었다.

console.log("I'll execute first!");
setTimeout(() => {
   console.log("I'll execute only when stack is empty :(");
}, 0);
console.log("I'll execute second");

처음에는 타임 아웃 콜백이 거의 즉시 실행되는 것으로 보입니다. 그러나 타이머 자체는 콜백 대기열에 콜백을 넣지 않습니다.
타이머가 만료되면 환경은 콜백을 비어있을 때 호출 스택에 푸시 된 큐에 넣습니다.

따라서 setTimeout은 지정된 시간 후에 콜백을 실행하지 않습니다. 오히려 콜백이 실행되는 최소 시간을 보장합니다.
타임 아웃이 0초이면 타이머는 거의 즉시 만료되고 콜백은 콜백 대기열에 배치됩니다.
그러나 이벤트 루프는 콜백을 푸시하기 전에 스택이 비어있을 때까지 기다려야합니다.
이것은 스택이 클 때까지 기본적으로 콜백 실행을 연기한다는 것을 의미합니다.

결론

프로그램이 실행되는 환경을 이해하면 개발자로서 효율성과 효율성이 크게 높아질 수 있습니다.
주어진 프로그램이 왜 그렇게 작동하는지에 대한 논리를 강조합니다.
누락 된 것이 있거나 게시물이 마음에 들면 의견에 대해 알려주십시오.
또한 Github 또는 Twitter에서 나를 팔로우하여 프로그래밍 및 개발 전반에 대한 통찰력을 공유 할 수 있습니다.

Clone this wiki locally