프론트엔드 개발/JavaScript

자바스크립트) 자바스크립트 동작원리(콜스택/메모리 힙 구조)

Ella Seon 2023. 7. 13. 08:51

0. 동기와 비동기

- 현재 실행 중인 태스크가 종료될 때까지 다음에 실행될 태스크가 대기하는 방식 을 동기(synchronous) 처리 방식이라고 하며
- 현재 실행 중인 태스크가 종료되지 않은 상태라 해도 다음 태스크를 곧바로 실행하는 방식 을 비동기(asynchronous) 처리라고 한다.

(오해하면 안되는건, 비동기는 동시의 문제가 아니다. 순서의 문제다. 비동기도 정해진 순서가 있다)

- 한번 비동기는 영원한 비동기다. 
- 대표적으로 타이머 함수인 ① setTimeout/ setInterval ② HTTP 요청 ③ 이벤트 핸들러 는 비동기 처리 방식으로 동작한다.

 

1. 자바스크립트 엔진

- 자바스크립트 엔진은 자바스크립트 코드를 이해하고 실행을 도와줌
- 자바스크립트 엔진은 Memory Heap과 Call Stack으로 구성되어 있고 이 메모리 구조를 통해 데이터 및 코드 실행을 관리한다. (ex. 크롬 V8 Engine)

 

콜스택 메모리힙
1) 호출된 함수가 콜스택에 push 되는 곳.
2)원시타입 데이터가 저장되는 곳
3) 실행컨텍스트(Excution Context)를 통해 변수식별자 저장/스코프체인 및 this 관리/ 코드 실행순서 관리 등을 수행
4) 메모리 힙을 참고하여 코드 실행에 필요한 변수, 함수 등의 위치를 참조하며 실행함
1) 선언된 변수들이 메모리 할당이 이루어지는 곳
2) 참조 데이터가 저장되는 곳

메모리에 값을 저장하려면 먼저 값을 저장할 메모리 공간의 크기를 결정해야 한다. 참조데이터는 원시 값과는 달리 크기가 정해져 있지 않으므로 할당해야 할 메모리 공간의 크기를 런타임에 결정(동적 할당)해야 한다. 따라서 객체가 저장되는 메모리 공간인 힙은 구조화되어 있지 않다는 특징이 있다.

출처 : 카레유 블로그

- 자바스크립트 엔진은 단 하나의 실행컨텍스트 스택구조(단일 스레드)를 사용하기 때문에 실행컨텍스트가 종료 되어 콜스택에서 제거되기 전까지는 다른 어떤 테스크도 실행되지 않는다. 실행컨텍스트의 최상위 요소인 '실행 중인 실행컨텍스트'를 제외한 모든 실행컨텍스트는 모두 대기중인 태스크 들이다. 대기중인 태스크들은 현재 실행중인 실행컨텍스트가 pop 되어 실행 컨텍스트 스택에서 제거 되면, 비로소 실행된다.

단일 콜스택을 갖는 다는 의미는 요청이 동기적으로 처리된다는 것을 의미한다. 즉, 현재 실행하고 있는 함수가 있는 경우에 다른 일을 할 수 없고, 다른 일들이 블락되게 되는 것.이렇게 되면 브라우저에서 오래 걸리는 작업이 실행될 경우, 웹페이지 UI 는 멈춰버리고 사용자는 어떤 행동도 할 수 없게 됨

(ex: 1분 이상이 시간이 소요되는 오래 걸리는 작업이 있다고 가정 해보자. 동기적이기 때문에, 1분 동안 아무것도 안하고 기다리면 브라우저는 아무런 행동(화면 스크롤, 버튼 클릭…)도 하지 못하게 된다. 그렇다면 어떻게 해야할까요? 이 때 사용하는 것이 바로 비동기 콜백이다. 비동기 요청을 처리하기 위해서는 자바스크립트를 실행하는 환경인 브라우저(Web API) 나 Node.js 가 담당한다.

 

🔸Web API

- Web API 는 JS 엔진이 아니다.

- Web API는 '브라우저에서 제공하는 API' 로 DOM, AJAX,Timeout 등이 있다.

- Call Stack 에서 실행된 비동기 함수는 Web API 를 호출하고, Web API 는 콜백함수를 Callback Queue 에 넣는다. 

 

🔸콜백큐 (마이크로 태스크큐>태스크큐)

자바스크립트 런타임 환경에서 처리해야하는 명령어를 임시로 저장하는 대기 큐로 비동기적으로 실행된 콜백함수가 보관되는 영역이다. 예를들어 setTimeout에서 타이머 완료 후 실행되는 콜백함수, addEventListener에서 click 이벤트가 발생했을 때 실행되는콜백 함수들이 보관된다.

 

마이크로 태스크 큐는 태스크 큐보다 우선순위가 높다. 따라서, 이벤트 루프에서 마이크로 태스크큐에 쌓인 태스크를 먼저 콜스택에 올려준 뒤, 태스크 큐에 잔여 태스크를 콜스택에 올린다. 

콜백 큐 종류
Task Queue 에 들어가는 콜백함수 Microtask Queue 에 들어가는 콜백함수
1) setTimeout / setInterval
2) HTTP 요청
3) 이벤트 핸들러의 콜백함수나
이벤트 핸들러는 태스크 큐에 일시 저장된다.
Promise(then), MutationObserver
setTimeout(() => console.log(1), 0);

Promise.resolve()
  .then(() => console.log(2))
  .then(() => console.log(3));

프로미스의 후속 처리 메서드(then, catch, finally)도 비동기적으로 동작하므로 1 > 2 > 3 의 순으로 출력될 것처럼 보이지만 2 > 3 > 1 순으로 출력된다. 프로미스의 후속 처리 메서드의 콜백함수는 마이크로 태스크 큐에 저장되기 때문이다(마이크로태스크큐가 태스크큐보다 우선순위이기 때문)

 

더보기

🔸gif 로 이벤트 흐름 파악하기

  1. console.log("1") 실행
  2. setTimeout()의 동작을 브라우저 혹은 nodejs와 같은 런타임으로 위임
  3. 하지만 타이머가 0초이므로 바로 Callback함수를 Task Queue에 적재
  4. console.log("3") 실행
  5. 첫번째 Promise 실행 -> Microtask Queue에 callback 함수 적재
  6. 두번째 Promise 실행 -> Microtask Queue에 callback 함수 적재
  7. console.log("6") 실행
  8. Call Stack이 비었음
  9. Event loop가 Microtask Queue에 쌓인 Task를 call stack에 적재
  10. Microtask Queue가 전부 비워짐
  11. Event loop가 Task Queue에 쌓인 Task를 call stack에 적재

이처럼 우선순위가 제일 높은 Queue는 Microtask Queue입니다.
Microtask Queue가 전부 비워지고 나면 TaskQueue를 비우게 된다.
하나 주의해야할 점은 Microtask Queue는 한번 비워질때 Queue에 쌓인 모든 task를 비우지만, Task Queue는 한번에 Callback함수 하나씩을 실행한다.
그래서 하나의 작업을 call stack에 올려서 실행하고나서 다른 작업이 들어오면 우선순위가 밀리게 된다.

 

2. 이벤트 루프

- Call Stack 과 Callback Queue의 상태를 체크하며, Call Stack 이 빈상태가 되면, Callback Queue(FIFO)의 첫번째 콜백함수를 Call Stack 에 넣는다. 이러한 행위를 반복하기 때문에 루프라고 한다. 

 

🔸이벤트 루프 예제 코드 및 실행 순서

1. 자바스크립트 코드가 실행되고 함수가 호출되면, 호출 된 함수는 `Call Stack`에 쌓인다.
2. Stack의 `후입선출(LIFO)`의 룰에 따라 제일 마지막에 들어온 함수가 먼저 실행되며, 실행된 함수는 Call Stack에서 제거된다. Stack에 쌓여진 함수가 모두 실행된다.

3. 만약 setTimeout과 같은 비동기 함수가 실행된다면, Web API가 호출되고 Call Stack에서 제거된다.
4. 타이머가 종료되면 setTimeout의 콜백 함수를 CallBack Queue에 넣는다.
5. Event Loop는 Call Stack이 빈 상태가 되면 Callback Queue에 있는 첫번째 콜백을 Call Stack으로 이동시킨다.

 

🔸그림으로 이해하는 비동기 과정

1) 코드를 실행하면 실행되는 코드 자체를 말하는 main() 함수를 스택에 집어넣게 된다. 그러고 나서, console.log(‘Hi’)가 실행되고 스택에 쌓인 후, hi가 콘솔창에 출력된다.

2) hi 가 출력이 되면, 스택에서 console.log(hi)가 사라지고, setTimeout 비동기 함수를 콜스택에 쌓는데, web API가 호출되고(web API에서 타이머를 실행시키고 카운트 다운을 시작한다) 스택에서 setTimeout은 사라진다. 

3) console.log(‘JSConfEU’)를 실행하고 스택에 추가되고, ‘JSConfEU’를 출력 후 스택에서 지워진다. 이제 web API에서 실행하고 있는 타이머만 남았다. 모든 Web Api는 작동이 완료되면 콜백함수를 콜백큐(태스크큐) 에 집어넣는다. 
4) 콜스택 자리가 비면 콜백큐(태스크큐)에 있는 콜백함수가 콜스택에 들어간다. 

5) 다시 자바스크립트 엔진(V8)으로 돌아가서, console.log(‘there’)을 실행한다. 
이제 콘솔창에 there이 찍히고 stack 에서 모두 사라진다. 

 

 

 

🔶이벤트 루프 실행 링크

아래 페이지에서 콜스택, Web Apis, Callback Queue 과정을 볼수 있다.

http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D 

 

http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7%21%21%21PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D

 

latentflip.com

 


참고자료

https://curryyou.tistory.com/276

 

[자바스크립트] 콜스택/메모리힙 구조, 데이터 저장/참조 원리

이 글은 자바스크립트의 콜스택/메모리힙에서 데이터가 어떤 방식으로 저장되고 참조되는지를 정리한다. 기본적인 메모리 구조와 변수 생성 원리에 대해서는 아래 글을 꼭 참고(필수) [자바스

curryyou.tistory.com

https://ljtaek2.tistory.com/129?category=897337

https://www.youtube.com/watch?v=0q_h594NDMU&list=PLcqDmjxt30Rt9wmSlw1u6sBYr-aZmpNB3&index=12