콜 스택 — 지금 실행 중인 함수를 쌓아 두는 곳
자바스크립트가 "지금 어디를 실행 중인지" 기억하는 방법. 에러 메시지의 스택 트레이스가 바로 이것이다.
Seobway · · 6분
함수가 끝난 뒤 어디로 돌아가는지 누가 기억하는가
c() 가 끝나면 b() 로, b() 가 끝나면 a() 로 돌아가야 한다. 이 "돌아갈 자리"를
기억하는 것이 콜 스택이다.
function a() { b(); console.log("a 끝"); }
function b() { c(); console.log("b 끝"); }
function c() { console.log("c 실행"); }
a();
// c 실행
// b 끝
// a 끝 ← 들어간 순서의 반대로 끝난다
들어간 순서와 끝나는 순서가 반대다. 이게 스택의 성질이다.
콜 스택은 어떻게 동작하는가
콜 스택은 실행 중인 함수 호출을 쌓아 두는 후입선출(LIFO) 자료구조다.
함수를 호출하면 위에 쌓이고(push), 반환하면 위에서 빠진다(pop).
가장 위에 있는 것이 지금 실행 중인 함수다. 자바스크립트는 스레드가 하나라
콜 스택도 하나뿐이고, 따라서 한 번에 한 함수만 실행한다.
인터랙티브 다이어그램: a() → b() → c() 가 쌓였다 빠지는 과정 (단계별로 재생할 수 있다)
- a() 를 호출한다. 스택 맨 아래에 쌓인다.
- a 안에서 b() 를 호출한다. a 위에 쌓인다.
- b 안에서 c() 를 호출한다. 맨 위가 c 다 — 지금 실행 중인 함수.
- c 가 출력하고 반환한다. 맨 위에서 빠진다.
- 이제 맨 위는 다시 b 다. b 가 남은 줄을 실행하고 반환한다.
- a 도 남은 줄을 실행하고 반환한다. 스택이 비었다.
- 스택이 비면 자바스크립트는 다음 할 일을 찾는다.
에러 메시지의 스택 트레이스가 곧 콜 스택이다
에러가 났을 때 보이는 그 목록은 에러가 난 순간의 콜 스택을 위에서부터 찍은 것이다.
Uncaught TypeError: Cannot read properties of undefined
at c (app.js:9) ← 여기서 터졌다
at b (app.js:5) ← c 를 부른 곳
at a (app.js:1) ← b 를 부른 곳
위에서 아래로 읽으면 터진 지점 → 그것을 부른 곳 순서다. 원인을 찾을 때는
맨 위부터 보고, 내 코드가 아닌 라이브러리 프레임은 건너뛴다.
직접 확인
스택은 무한하지 않다. 끝나지 않는 재귀를 넣으면 한계를 눈으로 볼 수 있다.
function depth(n = 1) {
return depth(n + 1); // 반환하지 않으니 계속 쌓인다
}
try {
depth();
} catch (e) {
console.log(e.constructor.name + ":", e.message);
}
// 얼마나 깊이 쌓이는지 세어 보자
let count = 0;
function measure() {
count++;
measure();
}
try { measure(); } catch { console.log("최대 깊이 약", count);
}
깊이는 브라우저와 상황에 따라 다르다. 값 자체보다 스택에 한계가 있다는 사실이 중요하다.
흔한 실수
한 줄 정리
콜 스택은 실행 중인 함수 호출을 쌓아 두는 후입선출 구조이고, 자바스크립트에는
하나뿐이다. 그래서 한 번에 한 함수만 실행되고, 스택이 비어야 비동기 작업이 시작된다.
- 왜
a 끝이c 실행보다 나중에 찍히는지 설명할 수 있다 - 스택 트레이스를 위에서 아래로 읽는 법을 안다
-
Maximum call stack size exceeded가 무슨 뜻인지 안다
참고
- [1] Call stack — MDN Web Docs 용어 사전
- [2] RangeError: Maximum call stack size exceeded — MDN Web Docs
- [3] Executable Code and Execution Contexts — ECMAScript 사양
관련 글
- 실행 컨텍스트 — 함수가 실행될 때 만들어지는 환경 → — 스택에 쌓이는 것의 정체
- 이벤트 루프, 눈으로 따라가기 → — 스택이 빈 뒤에 일어나는 일
- javascript
- 콜스택
- 실행