[JS핵심개념33] 1. call stack

CHAI53·2019년 12월 1일
0

결국 프론트엔드 개발자가 궁극적으로 파야할 것은 자바스크립트라는 말은 전부터 계속 들어왔지만 리액트에 신경쓰며 페이지 구현에 급급하다 보니 자바스크립트를 파는건 뒤로 자꾸만 미뤄졌다.
속깊은 자바스크립트 책도 사서 읽어봤는데 너무 어려웠고. 그렇게 자바스크립트는 돌덩이처럼 나한테 턱하니 얹혀져만 있었다.
그래서 노마드 코더 유튜브 채널을 둘러보다 발견한 자바스크립트 개발자라면 꼭 알아야하는 33가지 핵심개념 레포를 바탕으로 해서 하나씩 하나씩 당장은 깊지 않더라도 정리를 해보기로 했다. 당장은 완전히 이해가 안되더라도 점차 첨삭해 나가면 되는 거니까.

why javascript?

요즘 웹 개발자는 브라우저 내의 상호작용 액션부터 컴퓨터 게임, 데스크탑 위젯, 크로스 플랫폼 모바일앱, 데이터베이스와 연결되는 서버사이드 코딩 등 모든 것들을 자바스크립트로 구현한다.
자바스크립트 생태계는 그 어느 때보다 복잡해졌고 앞으로 더욱 복잡해질 것이다. 따라서 자바스크립트 프레임워크나 라이브러리를 사용하려면 기저수준에서 이 모든 것들이 어떻게 이루어지는지를 알 필요가 있다.

javascript

자바스크립트는 싱글스레드 언어로, 한 번에 하나의 작업만 다룰 수 있다. 이는 멀티스레드 언어로 한번에 여러가지 작업을 동시 구현할 수 있는 python과 비교될 수 있다. 자바스크립트는 힙, 큐와 콜스택으로 구성된다. 이것은 크롬의 런타임 'V8'내부에 구현되어 있다.

image.png

콜스택, 힙, 큐가 각각 무엇인지 알아보도록 한다.

1. Call stack

함수의 호출을 기록하는 자료구조이다. 어떤 함수를 실행시키면 스택 위에 무언가를 올리는(push) 행위를 하는 것이다. 그리고 함수로부터 반환을 받거나 실행시킬때는 스택의 맨 위를 가져오는(pop) 것이다. 이는 카드게임의 개념과 비슷하다. 카드게임을 할 경우 카드는 항상 맨위에 올려놓고 맨위에서 가져오며 게임 도중에 중간이나 밑의 카드에 손을 대는 경우는 거의 없다.

image.png

코드를 실행시킬 때 가장 먼저 하는 일은 모든 실행이 시작되는 메인 함수를 찾는 일이다. 위의 예에서는 console.log(bar(6));가 먼저 콜스택에 올라간다. 그 이후에는 bar 함수가 parameter와 함께 스택의 맨 위로 올라가고 foo 함수는 스택의 맨 위로 올라갔다가 바로 값을 반환하고 빠진다(pop). 그 다음에는 bar 함수가 빠지고 console.log(bar(6)); 구문은 값을 출력한다.

image.png

브라우저 콘솔에서 가끔 긴 빨간색 에러 스택들을 볼 수 있다. 이는 콜스택의 현재 상태이다. 실행에 실패한 함수를 스택처럼 top부터 bottom까지 나타내는 것이다.

가끔 함수를 재귀적으로 부르다가 무한루프에 빠지는 경우가 있다. 크롬 브라우저는 16000 프레임의 제한된 스택을 가져, 이 범위를 넘어서면 Max Stack Error Reached라는 상태가 되고 실행 중이던 코드를 종료한다.

image.png

2. Heap

객체들은 힙 내부에 할당된다. 힙은 거의 구조화되지 않은 영역(unstructured)의 메모리다. 변수(variables)와 객체(object)들의 메모리 할당이 여기서 일어난다.

3. Queue

자바스크립트 런타임은 메시지 큐를 갖고 있다. 메시지 큐는 실행될 콜백함수나 실행될 메시지들에 대한 리스트이다. 스택이 충분한 공간을 갖고 있을 때, 메시지는 큐 밖으로 나오게 되고 메시지가 가지고 있던 함수 목록들이 실행된다. 이렇게 초기 스택이 만들어지고 함수가 차례로 실행되어 스택이 다시 비면 메시지 수행도 끝난다. 마우스 클릭, HTTP 요청 등과 같은 이벤트에 대한 콜백 함수가 제공되었다 가정하면 이 메시지들은 외부 비동기 이벤트들에 대한 응답으로 큐에 쌓인다. 그러나 사용자가 버튼을 눌렀을 때 아무런 콜백함수도 걸려있지 않다면 어떠한 메시지도 큐에 들어가지 않는다.

Event loop

일반적으로 자바스크립트 코드 성능 측정시, 스택 안에 위치한 함수는 성능을 느리게도 빠르게도 만든다. 만약 console.log("I love js"); 과 같은 단순한 코드만 있다면 속도는 빠르다. 하지만 수천 수백만개가 넘는 for문 또는 while문과 같은 반복문이 존재한다면 속도가 매우 느려질 것이다. 또 그 코드들은 스택을 계속 차지하고 있을 것이다. 이런 것들을 'Blocking Script'라 한다.

앞에서도 말했듯이 자바스크립트는 싱글스레드 언어라 한번에 하나의 작업밖에 처리하지 못하고 그 작업은 콜스택에 쌓여진 순서대로 이루어진다. 네트워크나 이미지 요청에 따른 응답은 응답자의 사정에 따라 아주 많이 느려질 수 있다. 만일 이러한 요청들이 동기화 함수들을 통해서 이뤄진다면, 그리고 그 사이에 렌더링을 일으키는 버튼을 클릭한다면 어떻게 될까? 이 경우 스택에 쌓인 함수들이 어떠한 값을 반환하기 전까지 다음 단계로의 진행이 불가능하다. 그리고 웹페이지는 브라우저가 아무 것도 할 수 없기 때문에 완전히 망가질 것이다.
다행히도 이러한 요청들은 비동기 함수인 AJAX를 통해 처리할 수 있다.

image.png

비동기 콜백을 이용한다는 것은 코드의 일정 부분을 실행시키고 나중에 실행될 콜백함수를 스택에 넣는 것을 의미한다. 비동기형 콜백의 예에는 $.get(), setTimeout(), setInterval(), Promises 등이 있다. 사실 노드는 비동기 함수 실행이 전부라고 할 정도이다. 모든 비동기 콜백들은 코드에서 읽히자마자 바로 실행되지 않고 잠시 후에 실행된다. 그래서 동기 함수들과는 다르게 바로 스택의 내부로 push될 수 없다. 그렇다면 비동기 콜백은 대체 어디로 가고 어떻게 다뤄지는 걸까?

image.png

위의 코드에서 자바스크립트의 네트워크 액션 요청을 보자.

요청 함수가 실행됩니다. 요청이 들어온 때에 실행될 콜백으로 onreadystatechange 이벤트 안에 있는 익명의 함수를 넘긴다.
"Script call done"은 동기 함수이기 때문에 바로 콘솔의 output에 들어간다. 비동기 함수가 실행될 때가 됐을 때, 서버로부터 응답이 오고 body부분을 콘솔에 출력하며 콜백이 실행된다.
응답(response)에서의 호출자(caller)의 분리는 자바스크립트 런타임이 비동기 명령이 완료되고 콜백이 호출될 때까지 기다리는 동안 다른 일을 하는 것을 허용한다. 여기에서는 브라우저 API들이 작동한다. DOM events, http request, setTimeout과 같은 비동기 이벤트들을 다루기 위한 브라우저의 웹 API threads는 브라우저 내부에 C++로 구현되어 있다. 위와 같은 웹 API들은 스스로 자신들의 실행코드를 스택에 넣을 수 없다. 현재 실행 중인 코드가 끝난다면 웹 API중 어느 하나가 콜백을 큐에 넣는다. 이벤트 루프는 큐 안의 콜백들을 스택이 비었을 때 밀어넣는 일을 담당한다. 이벤트 루프가 하는 기본적인 일 중에 하나는 스택과 작업 큐를 보고 스택이 비었을 때 큐에 첫번째에 있는 콜백을 스택에 밀어넣는 일이다. 다른 메시지가 들어오기 전에 각각의 메시지 또는 콜백들은 작업을 완료한다.

image.png

메시지들은 웹 브라우저에서 언제든 이벤트가 발생했을 때 추가된다. 그리고 이벤트에는 이벤트 리스너가 붙어있다. 만일 리스너가 없다면 발생한 이벤트는 그냥 사라진다. 예를 들어 웹 브라우저에서 어떤 요소를 클릭했을 때, click event handler는 큐에 메시지를 추가한다. 웹브라우저의 다른 이벤트들도 동일하게 작용한다. 이러한 콜백 함수 호출은 콜스택 안에서 초기 프레임의 역할을 한다. 그리고 자바스크립트는 싱글스레드이기 때문에, 추가적인 폴링 중 메시지와 프로세싱은 잠시 중단되고 스택에 있는 모든 호출들의 return을 기다린다. 그리고 동기 함수들은 스택에 새로운 콜 프레임들을 추가한다.

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

profile
꾸준함의 힘

0개의 댓글