본문 바로가기

WEB개발/JS&HTML

[JS] Engine, Event Loop

1. JS Engine

자바스크립트 엔진은 Memory Heap Call Stack으로 구성되어 있다
 ex) 구글의 V8 Engine(C++로 구성), WebKit Engine(Safari)이다.

 

자바스크립트는 단일 스레드 (sigle thread) 프로그래밍 언어인데,
이 의미는 Call Stack이 하나라는 이야기이다.

  • Memory Heap : 메모리 할당이 일어나는 곳
    (ex, 우리가 프로그램에 선언한 변수, 함수 등이 담겨 있음)
  • Call Stack : 코드가 실행될 때 쌓이는 곳. stack 형태로 쌓임.

 Stack(스택) : 자료구조 중 하나, 선입 후출(LIFO, Last In First Out)의 룰을 따른다

 

2. Meomory Heap

: Memory Heap은 객체를 저장하는 곳이다.

 

  • 객체 (Object) : 객체는 크기가 고정되지 않고, 동적으로 프로퍼티를 추가하거나 삭제할 수 있습니다. 이런 객체는 힙에 저장되고, 스택에는 그 객체를 가리키는 참조값(메모리 주소)만 저장됩니다.

  • 배열 (Array) : 배열은 크기가 동적으로 변경될 수 있기 때문에 힙에 저장됩니다. 스택에는 배열의 참조값이 저장됩니다.

  • 함수 (Function) : 함수 역시 객체로 취급되므로 힙에 저장됩니다. 특히 클로저 같은 경우, 함수가 생성될 때 참조된 외부 변수들도 힙에 저장됩니다.

  • 클래스 인스턴스 : ES6 클래스의 인스턴스는 객체로서 힙에 저장됩니다.
     
  • 복합 데이터 구조 : 객체나 배열이 포함된 복합 데이터 구조(예: 객체 안의 배열, 배열 안의 객체 등)도 모두 힙에 저장됩니다.

 

 

클로저는 함수가 생성될 때의 외부 변수들을 기억하고, 그 함수가 나중에 호출되더라도 이 기억된 변수들에 접근할 수 있는 기능
function outerFunction() {
    let outerVar = "I am outside!";
    
    function innerFunction() {
        console.log(outerVar);  // 외부 함수의 변수를 참조
    }
    
    return innerFunction;  // 내부 함수를 반환
}

const closure = outerFunction();  // outerFunction() 호출 시점에 outerVar를 기억
closure();  // "I am outside!" 출력​

 

3. Call Stack 

 함수 호출은 프레임들의 스택을 형성합니다. 호출 순서대로 스택에 적재합니다.

주의할 사항은 재귀를 사용할 경우 overflow 발생 위험이 있습니다.

 

 Tail recursion(꼬리 물기 최적화 : 다음 연산에 필요한 값을 다음 루틴에 넘기면 호출 당했던 곳으로 돌아와 연산을 거칠 필요가 없다) 과 같은 방법으로 회피는 가능하다고 하지만 아직 V8에서는 지원을 하지 않는 것인지 아래코드를 수행해도 오버플로우는 피할 수 없었다.

function factorial2(n) {
  function cal(n, result) {
    return n === 0 ? result : cal(n - 1, n * result);
  }

  return cal(n, 1);
}

> Uncaught RangeError: Maximum call stack size exceeded

4. Web API

그림의 오른쪽에 있는 Wep API는 JS Engine의 밖에 그려져 있다.
즉, 자바스크립트 엔진이 아니다.


Web API는브라우저에서 제공하는 API로, DOM, Ajax, Timeout 등이 있다.
Call Stack에서 실행된 비동기 함수는 Web API를 호출하고,
Web API는 콜백함수를 Callback Queue에 밀어 넣는다.

 

Browser API는 웹 브라우저에 내장되어 있으며, 브라우저 및 주변 컴퓨터 환경에서 데이터를 제공하거나 복잡한 작업을 수행 할 수 있습니다. 예를 들어, Web Audio API 는 브라우저에서 오디오를 조작하기 위한 Javascript 구성을 제공합니다.

 

  • 웹 콘텐츠 조작을 위한 DOM
  • 서버에서 데이터를 가져오기 위한 XMLHttpRequest 및 Fetch
  • 그래픽 그리기 및 조작을 위한 Canvas 및 WebGL
  • 오디오 및 비디오 조작을 위한 Web Audio 및 WebRTC
  • 웹 브라우저에 데이터를 저장하기 위한 Web Storage 

5. Callback Queue  (이벤트 큐(Event Queue), 콜백 큐(Callback Queue) )

비동기적으로 실행된 콜백함수가 보관 되는 영역입니다.
예를 들어 setTimeout에서 타이머 완료 후 실행되는 함수(1st 인자), addEventListener에서 click 이벤트가 발생했을 때 실행되는 함수(2nd 인자) 등이 보관된다.

  • Queue(큐) : 자료 구조 중 하나, 선입선출(FIFO, Frist In Frist OUT)의 룰을 따른다.

 

 

 

function fnc1() {
	for(var i=0 ; i < 500000000 ; i++) { }
	console.log("fnc1");
}

function fnc2() {
	setTimeout(()=>  console.log("fnc2"),1000);
}

 

 

 

> fnc2()수행 후 fnc1()수행하면 fnc1()의 수행을 마친 다음 fnc2()의 console.log가 수행된다.

 

즉, 1초 그 이상이 걸릴 수 있다.

6. Event Loop

 Event Loop는 JavaScript의 비동기 작업을 처리하는 핵심 메커니즘으로, 싱글 스레드 언어인 JavaScript가 비동기적으로 작업을 수행할 수 있게 해줍니다. Event Loop는 콜 스택(call stack), 큐(queue), 그리고 웹 API(web APIs)의 상호작용을 통해 비동기 코드(예: 타이머, HTTP 요청, 이벤트 처리기 등)를 처리합니다.

 

 

  • 동기적 작업:
    • 함수가 호출되면, 그 함수는 콜 스택에 쌓여 동기적으로 처리됩니다.
    • 동기 작업이 끝나면 스택에서 제거됩니다.
  • 비동기 작업:
    • setTimeout, HTTP 요청, DOM 이벤트 등 비동기 작업은 웹 API에 의해 처리됩니다.
    • 비동기 작업이 완료되면, 해당 작업의 콜백이 이벤트 큐에 추가됩니다.
    • 이벤트 루프는 콜 스택이 비어 있을 때, 이벤트 큐에서 콜백을 가져와 실행합니다.

 

 

7. 쓰레드관계

 

1) JavaScript Call Stack

  • 싱글 스레드:
    • JavaScript 코드는 단일 스레드에서 실행됩니다.
    • 모든 동기 작업(함수 호출, 변수 선언 등)은 **Call Stack(호출 스택)**에서 순차적으로 처리됩니다.
    • JavaScript 엔진(예: V8)은 이 Call Stack을 관리합니다.

2) Web API

  • 브라우저가 관리:
    • Web API는 브라우저나 Node.js 런타임에서 제공되며, JavaScript 엔진 외부에서 동작합니다.
    • Web API에서 비동기 작업은 브라우저의 다른 시스템(네트워크 스레드, 타이머 등)에서 처리됩니다.

주요 예:

  1. setTimeout 및 타이머:
    • 타이머는 브라우저의 타이머 관리 시스템(Web API)에서 처리합니다.
    • 타이머 만료 후 콜백은 Task Queue에 추가됩니다.
  2. 네트워크 요청(fetch, XMLHttpRequest):
    • 네트워크 작업은 브라우저의 네트워크 스레드에서 실행됩니다.
    • 요청이 완료되면 브라우저가 결과를 Task Queue나 Microtask Queue에 추가합니다.
  3. Web Workers:
    • Web Worker는 완전히 별도 스레드에서 실행됩니다.
    • 메인 스레드와 독립적으로 동작하며, 메시지 전달을 통해 통신합니다.

3) 이벤트 처리 및 이벤트 루프

  • 싱글 스레드에서 관리:
    • 이벤트 루프는 JavaScript 엔진이 아닌 브라우저가 관리하지만, 이벤트 콜백을 처리하는 실행은 **싱글 스레드(Call Stack)**에서 이루어집니다.
    • 이벤트 루프는 다음 작업을 수행합니다:
      1. Call Stack이 비어 있는지 확인.
      2. Task Queue 또는 Microtask Queue에서 작업을 가져와 Call Stack에 추가.

 

스레드 간 관계

  1. 메인 스레드:
    • JavaScript의 Call Stack 실행.
    • Task Queue 및 Microtask Queue에서 콜백을 가져와 실행.
  2. Web API 스레드:
    • 타이머, 네트워크 요청 등 브라우저의 비동기 작업 처리.
    • 작업 완료 후 결과를 Task Queue나 Microtask Queue로 전달.
  3. 이벤트 루프:
    • 메인 스레드에서 실행되는 작업을 관리(스레드 역할이 아님).
    • Task Queue/Microtask Queue와 Call Stack을 연결.
  4. Web Workers:
    • 독립적인 별도 스레드.
    • 메인 스레드와는 메시지를 통해 통신.

 

console.log("Start");

setTimeout(() => {
    console.log("Timeout callback");
}, 1000);

fetch("https://api.example.com/data")
    .then(response => response.json())
    .then(data => console.log("Fetch complete"));

console.log("End");

 

  • 실행 순서:
    1. console.log("Start"): 호출 스택에 추가되어 바로 실행.
    2. setTimeout: Web API에 전달되어 타이머가 시작됨.
    3. fetch: 네트워크 요청을 Web API로 전달. 요청이 완료되면 Microtask Queue에 .then 콜백이 추가됨.
    4. console.log("End"): 호출 스택에 추가되어 실행.
    5. 타이머가 만료되면 Task Queue에 콜백 추가.
    6. 이벤트 루프가 Microtask Queue의 .then을 먼저 실행한 후 Task Queue의 타이머 콜백을 실행.

 


https://2dubbing.tistory.com/87 [비실이의 개발 성장기]

https://jongbeom-dev.tistory.com/119