LHJ

I'm a FE developer.

1.1 자바스크립트 함수 실행 이해 - 호출스택, 이벤트 루프, 작업 등

27 Sep 2020 » js

1.1 자바스크립트 함수 실행 이해 - 호출스택, 이벤트 루프, 작업 등

우리가 흔히 불려왔던 웹 개발자 또는 프론트엔드 엔지니어들은 요즘 모든 것을 한다.
컴퓨터 게임을 만들기 위한 브라우저의 인터렉티브한 코드부터 시작해서,
데스크톱 위젯, 크로스 플랫폼 모바일 앱, 그리고 데이터베이스에 연결하기위한 서버사이드에서 작동하는(제일 유명한 노드js) 코드까지 작성한다.

이렇게 자바스크립트는 스크립팅 언어로서 거의 모든 곳에서 사용되고 있다.

따라서 자바스크립트의 내부에 대해 알고 더 효율적이고 효과적으로 사용하는 것이 중요하며 이것이 이번 글의 주제이다.

자바스크립트 생태계는 그 어느 때보다 복잡해졌고 앞으로도 계속될 것이다.
최신 웹, 앱을 구축하는데 필요한 도구는 Webpack, Babel, ESLint, Mocha, Karma, Grunt… 등으로 압도적이다.

무엇을 사용해야할까? 당신은 어떤 도구를 사용하고 있나?

오늘날 웹 개발자들의 투쟁을 완벽하게 보여주는 웹 만화를 찾았다.

만화 내용
A : 나 고기가 먹고싶은데, 그냥 먹으면 될까?
B : 안돼! 불을 사용해서 요리해서 먹어!
A : 불은 뜨겁잖아! 내 손까지 익으면 어떻게해?
B : 뾰족한 막대기를 사용해서 요리하던지, 아니면 돌을 달궈서 그 위에 고기를 얹어서 익히던지, 아니면 박스 안에 불을 지펴!
A : 선택사항이 너무 많잖아, 거기서 내가 뭘 선택해야돼?
B : 모르지.


A : 오! 고기가 익었어, 나 지금 먹어도 되지?
B : 안되지! 기다려! 소금이랑 향신료를 뿌려야지!
A : … 고기를 먹기위해 불로 요리하고, 요리될 때까지 기다리고, 바위에 향신료까지 필요해? 난 단지 날것으로 먹으면 땡인데? 멍청해!
B : …

개발자 : 이러한 이유로 현대 자바스크립트가 너무 복잡하다고 생각해..

-Javascript Fatique : 자바스크립트를 배울때 느낌-


위와 같은 모든 것을 일단 제외해야된다.
자바스크립트 개발자가 개발할 때 프레임워크나 라이브러리를 사용하기 전에 먼저 해야할 일은 이 모든 것이 루트 수준에서 내부적으로 수행되는 방법의 기본 기반을 아는 것이다.
원론적인 내용을 알아야된다.

대부분의 자바스크립트 개발자는 크롬의 런타임인 V8이라는 용어를 들어봤을지 모르지만, 그게 무슨뜻인지 모를 것이다.
나는 신입 때 결과물을 만들어내는 거에 집중하느라 이러한 모든 멋진 개념에 대해 많이 알지 못했다.
그것은 이 헬 같은 자바스크립트가 모든 일을 어떻게 할 수 있는지에 대한 나의 궁금증을 충족시키지 못한 원인이었다.

나는 구글을 뒤지면서 자바스크립트에 대해 더 깊이 파고들기로 결심했고, 이벤트 루프에 대한 JSConf의 훌륭한 강연, 그리고 Philip Roberts를 포함하여 좋은 게시물 몇 개를 발견했다.
그래서 난 배운 내용을 요약하고 공유하기로 결정했다.

알아야 할 것이 많기 때문에 기사를 두 부분으로 나누었다.
이 부분에서는 일반적으로 사용되는 용어를 소개하고 두 번째 부분에서는 모든 용어를 연결하면서 설명하도록 하겠다.


자바스크립트는 단일 스레드 단일 동시 언어로, 한 번에 하나의 작업 또는 한 번에 코드 조각을 처리 할 수 있습니다.
과 같은 다른 부분과 함께 가 Javascript 동시성 모델 (V8 내부에서 구현 됨)을 구성하는 단일 호출 스택이 있습니다.
먼저 다음 각 용어를 살펴 보겠습니다.

1. 호출스택

기본적으로 프로그램에서 우리가있는 곳에 함수 호출을 기록하는 데이터 구조입니다.
실행할 함수를 호출하면 스택에 무언가를 푸시하고 함수에서 돌아 오면 스택의 맨 위를 팝(pop)합니다.

위 파일을 실행하면서 먼저 모든 실행이 시작될 주 함수(main function 또는 익명함수)를 찾습니다.
위의 경우 스택으로 푸시되는 console.log(bar(6))에서 시작하고, 그 위에있는 다음 프레임은 인수가 있는 함수 bar가 호출되고, 또 그 위에는 함수 foo를 호출합니다.
스택의 맨 위가 즉시 반환되므로 foo가 스택에서 튀어나오고 마찬가지로 bar가 튀어 나오고 마지막으로 콘솔 문이 튀어 나와 출력을 인쇄합니다.
이 모든 것은 한 번에 하나씩 순식간에(ms단위) 발생합니다.

여러분 모두는 브라우저 콘솔에서 가끔씩 긴 빨간색 오류 스택추적을 보았을 겁니다.
이것은 기본적으로 호출 스택의 현재 상태를 나타내고 함수에서 스택처럼 위에서 아래로 실패한 위치를 나타냅니다.

때때로 우리는 반복적으로 함수를 여러번 호출할 때 무한루프에 들어갑니다.
크롬 브라우저의 경우 스택 크기에 제한이 있습니다.
16,000 프레임입니다.
16,000 프레임이 넘어가면 스택 오류가 납니다.

2. Heap(힙)

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

3. Queue(큐)

자바스크립트 런타임에는 처리할 메시지 목록과 실행할 관련 콜백 함수인 메시지 큐(대기열)이 포함되어 있습니다.
스택에 충분한 용량이 있으면 관련 함수를 호출하여 초기 스택 프레임을 생성하는 메시지를 큐(대기열)에서 꺼내 처리합니다.
스택이 다시 비워지면 메시지 처리가 종료됩니다.

기본적으로 이러한 메시지는 콜백 함수가 제공된 경우 외부 비동기 이벤트(예: 마우스 클릭 또는 HTTP 요청에 대한 응답 수신)에 대한 응답으로 대기열(큐)에 추가됩니다.
예를 들어 사용자가 버튼을 클릭했는데 콜백 기능이 제공되지 않은 경우 대기열에 메시지가 추가되지 않았습니다.

Event Loop

기본적으로 console.log와 같은 함수의 속도는 빠르지만, 이를 수천번 수만번 반복하면 속도가 느려집니다.
콜스택(호출 스택)을 점거하기 때문입니다.

네트워크 요청, 이미지 요청과 같은 서버 요청은 다행히 비동기 함수인 AJAX를 통해 수행될 수 있습니다.
이러한 네트워크 요청이 동기 함수를 통해 이루어진다고 가정하면 어떻게 될까요?

네트워크 요청은 기본적으로 어딘가에있는 다른 컴퓨터의 서버로 전송됩니다.
이때 서버에선 응답을 느리게 보낼수도 있습니다.
그동안 해당 페이지의 버튼을 클릭하거나 다른 렌더링을 수행해야하는 경우, 스택이 차단되어 아무 일도 일어나지 않습니다.

Ruby와 같은 다중 스레드 언어에선느 처리할 수 있지만, 자바스크립트와 같은 단일 스레드 언어에서는 스택 내부의 함수가 값을 반환하지 않는한 불가능합니다.
브라우저가 아무것도 할 수 없기 때문에 웹 페이지가 무너질 것입니다.
사용자를 위한 유동적인 UI는 어떻게 만들까요?

“Concurrency in JS— One Thing at a Time, except not Really, Async Callbacks”
자바스크립트에서의 동시성(Concurrency) - 한번에 한개씩 실행, 실제로는 아닌.. Async Callbacks
이게 무슨 말이지.. 영어가 해석이 안되네..

가장 쉬운 해결책은 비동기 콜백을 사용하는 것입니다.
즉, 코드의 일부를 실행하고 나중에 실행할 콜백(함수)을 제공합니다.
우리 모두는 $.get(), setTimeout(), setInterval(), Promises 등을 사용하는 AJAX 요청과 같은 비동기 콜백을 경험해야 합니다.

노드는 비동기 함수 실행에 관한 것입니다.
이러한 모든 비동기 콜백은 즉시 실행되지 않으며 나중에 실행될 예정이므로 console.log()와 같은 동기 함수, 수학 연산과 달리 스택 내에서 즉시 푸시할 수 없습니다.
대체 어디로 가고 어떻게 처리되는 걸까요?

위 코드와 같이 Javascript에서 네트워크 요청이 작동하는 것을 보면 :

  1. 요청 함수가 실행되어 onreadystatechange 이벤트의 익명 함수를 콜백으로 전달하여 나중에 응답을 사용할 수있을 때 실행할 수 있게 했습니다.
  2. “Script call done” 메시지가 즉시 찍힙니다.
  3. 언젠가는 응답이 돌아오고 콜백이 실행되어 본문을 콘솔에 출력합니다.

응답에서 호출자를 분리하면 자바스크립트 런타임이 비동기 작업이 완료되고 콜백이 실행되기를 기다리는 동안 다른 작업을 수행할 수 있습니다.
이 장소(위의 비동기 작업이 완료되고 콜백이 실행되기를 기다리는 곳)는 브라우저 API(Web API)가 시작되고 API를 호출하는 곳입니다.
기본적으로 DOM 이벤트, http 요청, setTimeout 등과 같은 비동기 이벤트를 처리하기 위해 C++로 구현된 브라우저에 의해 생성되는 스레드입니다.

monkey는 이러한 API를 패치하여 런타임 변경 감지를 유발합니다.
이에 이를 달성할 수 있었던 방법을 알 수 있습니다.

브라우저 웹 API-DOM 이벤트, http 요청, setTimeout 등과 같은 비동기 이벤트를 처리하기 위해 C++로 구현된 브라우저에 의해 생성된 스레드입니다.

이제 이러한 WebAPI는 자체적으로 실행 코드를 스텍이 넣을 수 없습니다.
실행 코드가 있는 경우 코드 중간에 무작위로 표시됩니다.
위에서 설명한 메시지 콜백 대기열이 그 방법을 보여줍니다.
WebAPI는 실행이 완료되면 콜백을이 대기열로 푸시합니다.
이제 이벤트 루프는 대기열에서 이러한 콜백을 실행하고 비어있을 때 스택에 푸시합니다.
이벤트 루프 기본 작업은 스택과 태스크 큐를 모두보고 스택이 비어있는 것으로 표시 될 때 큐의 첫 번째 항목을 스택으로 푸시하는 것입니다.
각 메시지 또는 콜백은 다른 메시지가 처리되기 전에 완전히 처리됩니다.

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

Javascript Event Loop Visual Representation

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

다음 부분에서는 위의 절차에 대한 코드 실행의 시각적 애니메이션을 보여 주면서 작업, 마이크로 작업과 같은 다양한 유형의 비동기 함수가 무엇이며
대기열에서 우선 순위가 먼저 오는 것을 자세히 설명합니다.
또한, 제로 딜레이와 같은 해킹은 일부 기능을 수행하는 데 사용됩니다.