[JS 33가지 개념] 1. 함수의 동기적 호출과 콜 스택의 관계성

2020. 6. 11. 16:20Javascript/33가지 개념

 콜 스택은 함수의 호출을 기록하는 자료구조다. 우리가 함수를 선언하고 호출하게되면 호출된 함수는 스택에 쌓였다가 실행된다. 여기서 스택이란 LIFO(Last In First OUT)구조를 말하는데, 첫 번째로 들어오면 마지막에 나가고, 마지막에 들어오면 첫 번째로 나간다는 뜻이다.

다음 코드를 보자.

 

console.log("a");
console.log("b");
console.log("c");

 

 이 코드를 출력한다면? 어떤 출력 결과가 뜰까? 자바스크립트를 이용해 Hello world라는 메세지를 콘솔을 이용해서 출력해 본 사람이라면 "a", "b", "c" 순서로 출력이 뜰 것이라는 것은 알 것이다. 그런데 왜 이런 결과가 뜨는 것일까? 바로 콜 스택이라는 자료구조를 이용하기 때문이다. 그림으로 표현하면 다음과 같다.

 

 위 스크립트가 실행되면, 맨 첫번째로 콜 스택에는 main()이라는 실행 메서드가 푸쉬될 것이다.

 

[그림 1] 프로그램 시작시 콜 스택 상태

 

 그 다음에는 console.log("a")콜 스택에 쌓일 것이다. 그런데 console.log("a") 함수는 쌓이자마자 바로 a를 출력한다. a를 출력하면 제 기능이 끝나므로 콜 스택을 빠져나오게 된다.

 

[그림 2] 콘솔 함수가 콜 스택에 푸쉬되고 바로 팝 되는 과정

 

 console.log("b"), console.log("c") 함수도 console.log("a")와 마찬가지로 똑같이 동작한다. a, b, c 문자가 모두 출력되면 main()이라는 함수만이 콜 스택에 남게된다. 그리고 마지막으로 main()이라는 코드 실행 함수도 콜 스택을 빠져나온다. 그러면 콜 스택은 empty 상태가 된다.이는 프로그램 코드가 종료되었다는 것을 의미한다. 여기서 우리는 두 가지 사실을 알 수 있다.

 

  1. 함수 호출이 순서대로 일어났다.

  2. 스택은 데이터를 넣는 저장소이다.

 

 위 처럼 프로그램 함수의 동작이 순서대로 처리될 때 우리는 동기적이다이라는 표현을 사용한다. 그런데 앞에서 나는 스택이 첫 번째로 들어오면 마지막에 빠져나간다는 구조를 가지고 있다고 언급했다. 하지만 위 코드만으로는 스택이 데이터를 저장하는 저장소라는 것은 알 수 있으나 LIFO 구조라는 것은 입증할 수 없다. 따라서 이를 입증하기 위해 다음과 같은 코드를 작성해 보았다.

function c() {
  console.log("c");
}

function b() {
  console.log("b");
  c();
}

function a() {
  b();
  console.log("a");
}

a();

 

 위 코드의 출력은 어떻게 나올까? 정답은 b -> c -> a 순이다. 왜? 이 코드 역시 동기적으로 실행되기 때문이다. 위 코드의 실행 과정을 알아보기 위해 크롬 개발자도구(F12)를 키고 Source버튼을 누른 뒤 다음과 같이 브레이크 포인트를 걸어보도록 하겠다.  그리고 새로고침(F5)를 눌러보자.

 

[그림 3] 개발자도구 - 소스 - 브레이크포인트 - 새로고침

 

  새로고침을 누르면 다음과 같은 화면이 뜨면서 디버깅 모드로 전환된 것을 볼 수 있다.

 

[그림 4] 새로고침한 화면

 

 위 그림을 자세히 보자. 파란색 밑줄이 그어진 화살표를 마우스로 클릭하면 코드를 다음 단계로 진행시킬 수 있다. 또한 빨간색 줄이 그어진 곳은 콜스택을 가리킨다. 다음 단계로 진행시키다보면 호출된 함수들이 콜 스택으로 쌓여있는 것을 볼 수 있을 것이다. 이제 F11을 눌러보자.

 

[그림 5] 콜 스택안에 함수 a가 push됨

 

 그리고 Call Stack부분을 보면 익명의 함수(anoymous)라는 것을 볼 수 있는데, 이는 설명했던 메인함수라고 생각하면 된다. 그리고 익명 함수 위를 보면 a가 호출되어 바로 처리되지 않고 콜 스택에 임시적으로 저장되어 있는 것을 볼 수 있다. 다시 F11을 눌러보자.

 

[그림 6] 콜 스택안에 함수 b가 호출됨

 

 위 그림의 Call Stack부분을 보면 b가 들어와서 a 위에 올라가있다는 것을 알 수 있다. 여기서 한 번 더 F11을 누르면 console.log("b")가 들어왔다가 바로 b를 출력하고 빠져나왔기 때문에 콘솔 창에 b는 출력되있고, 콜 스택에 console함수는 들어가있지 않다. 그리고 또 F11을 눌러서 함수 c를 호출해보자.

 

[그림 7] 콘솔 창에 b가 출력되었고, 콜 스택에는 함수 c가 추가됨

 

 위 그림을 보면 콘솔창에는 b가 출력되있는 것을 볼 수 있고, 콜 스택에는 c가 추가되었음을 볼 수 있다. 여기서 F11을 누르게되면 console.log("c")가 실행되면서 콘솔창에 c가 출력될 것이다. 그리고 한 번 더 F11을 누르면 c함수는 자신의 할 일을 다 했기 때문에 콜 스택에서 빠져나오게 된다.

 

[그림 8] 콜 스택에서 함수 c가 빠져나옴

 

 함수 b의 역할은 b를 출력하고 c를 호출해서 실행시키는 것이다. 이미 b는 출력했고, c 함수가 호출되어 제 기능을 다하고 종료되었다. 이제 b의 역할은 끝났으니 콜 스택에서 빠져나오기만 하면 된다. 따라서 F11을 누르면 b는 콜 스택에서 빠져나오고 커서는 a 함수를 가리킬 것이다.

 

[그림 9] 콜 스택에서 a만 남음

 

  a함수 역시  두 가지 일이 있다. 첫째는 b함수를 실행시키는 것이고 둘째는 문자 a를 출력하는 것이다. 첫 번째 과정을 끝냈으니 두 번째 과정인 a를 출력하는 것만 남았다. F11을 누르면 문자 a를 출력할 것이다. 그리고 a 함수는 완전히 종료된다. 따라서 다시 F11을 누르면 a 함수도 콜 스택에서 빠져나올 것이다. 그리고 콜 스택에는 익명 함수(anoymous)가 남게 되고, 커서는 a()밑에 쪽을 가리킬 것이다.

[그림 10] 커서의 위치, 콜 스택에 뭐가 남아있는지 주목하고 왜 그런지 한번 생각해보자.

 

  그리고 F11을 누르게 되면 익명 함수(main)이 끝나게 되면서 프로그램이 완전히 종료된다. 이 과정을 통해 우리는  세 가지를 알 수 있다.

 

  1. 함수의 실행 순서는 콜 스택과 관련이 있다.

  2. 콜 스택에 의해 코드는 동기적인 순서로 실행될 수 있다.

  3. 스택은 자료를 저장하는 곳이며, LIFO(Last In, First Out)의 구조를 갖는다. 

그러면 다음과 같은 코드를 작성해서 일부로 에러를 일으키면

function c() {
  console.log("c");
}

function b() {
  console.log("b");
  throw Error("Hello I'm error! Fuck!");
  c();
}

function a() {
  b();
  console.log("a");
}

a();

[그림 11] 에러 메세지

 

 이런 에러 메세지를 볼 수 있다. 해석하자면 이 에러는 코드의 16줄에서 a함수를 호출했고, a함수는 코드 12줄에서 b함수를 호출했는데 b 함수안에 있는 7줄을 처리하는 과정에서 에러가 발생했다로 보면 될 것 같다. 그리고 콜 스택에는 처리되지 않은 a, b함수가 그대로 있다라는 내용도 알아두면 좋을 것 같다. 저 에러는 개발하다가 흔히 볼 수 있는 메세지다. 과거 저 에러를 봤을 땐, 단순히 에러가 발생했구나 정도로만 생각했는데, 이제는 원인이 무엇인지도 확실히 알 수 있다.

 또한 함수의 재귀호출을 할 때 빠져나오는 조건문을 설정하지 않을 경우 무한루프에 빠져 다음과 같은 메세지를 볼 수 있는데

 

[그림 12] 재귀 호출 에러 메세지

 

 이 에러 메세지는 콜 스택 용량이 꽉 찼다는 것을 얘기한다. 함수가 처리되지 않고 계속 쌓이다보니(크롬은 16000 프레임의 용량을 가진 콜 스택을 지원), 브라우저가 가지고 있는 콜 스택의 용량을 뛰어넘어서 다음과 같은 에러가 발생한 것이다. 

 

 

  이런 깊은 개념들을 알면 자바스크립트라는 프로그램 언어를 자연스럽고 쉽게 해독할 수 있을거라본다.

그렇게 될 때까지 많은 시간을 필요로 하겠지만... 계속 공부하다보면 남는게 있겠지.. 아무튼 긴 글 읽어주셔서 감사합니다.

 

참고자료

1. Jakeseo_me.log - 자바스크립트 개발자라면 알아야할 개념 #1 콜스택(번역)

2. Gaurav Pandvia - Understanding Javascript Function Executions - Call Stack, Event Loop, Tasks & more

3. 코드종 - 자바스크립트 타이머는 지각쟁이? 그 이유는 싱글 콜 스택?(유튜브)