이벤트 루프에 대한 미디움 글을 번역했습니다.
이벤트 루프는 코드 실행을 처리하는 연속적인 프로세스로, call stack, microtask queue, callback queue를 관리하여 자바스크립트에서 비동기 연산을 효율적으로 실행할 수 있도록 합니다.
이벤트 루프의 개념을 이해하기 위해 먼저 자바스크립트 코드의 내부 작동을 이해해야 합니다.
이 프로세스에 적극적으로 기여하는 다음의 기본 요소를 살펴보겠습니다.
자바스크립트에서 메모리 힙은 프로그램 실행 중 런타임 환경(예: 웹 브라우저 또는 Node.js)이 변수, 객체, 함수를 위해 공간을 할당하는 메모리 영역입니다.
메모리 힙에 대한 핵심 포인트 입니다.
힙은 프로그램 실행 중에 생성되는 객체와 데이터 구조에 대한 메모리를 동적으로 할당하는 역할을 담당합니다.
함수 호출과 로컬 변수에 대한 정적 메모리 할당을 처리하는 스택과는 대조적입니다.
객체, 배열 및 기타 복잡한 데이터 구조는 힙에 저장됩니다. 새 키워드나 객체 리터럴 등을 통해 자바스크립트에서 객체를 생성하면 해당 객체에 대한 메모리가 힙에 할당됩니다.
자바스크립트는 자동 가비지 컬렉션을 사용하여 힙의 메모리를 관리합니다. 가비지 컬렉션은 더 이상 사용되지 않는 메모리를 식별하고 해제하여 메모리 누수를 방지합니다. 이 과정에는 참조되지 않은 객체를 식별하고 해당 메모리를 회수하는 과정이 포함됩니다.
자바스크립트에서 가비지 컬렉션에 사용되는 기술 중 하나는 참조 카운팅입니다. 힙의 각 객체에는 참조 카운트가 있으며, 객체의 참조 카운트가 0으로 떨어지면 (프로그램에서 더 이상 도달할 수 없음을 의미) 해당 객체에 대한 메모리가 해제됩니다.
가비지 컬렉션은 메모리 관리에 도움이 되지만, 자바스크립트에서 의도치 않게 메모리 누수가 발생할 수 있습니다. 이는 객체에 대한 참조가 필요 이상으로 오래 지속되어 가비지 컬렉터에 의해 수집되지 않을 때 발생할 수 있습니다.
메모리 힙의 작동방식을 이해하는 것은 특히 대규모 어플리케이션이나 장기간 실행되는 프로세스를 다룰 때 효율적인 자바스크립트 코드를 작성하는데 중요합니다. 개발자는 객체 수명을 염두에 두고 참조를 주의 깊게 관리하며 잠재적인 메모리 누수 시나리오에 유의해야 합니다.
자바스크립트의 모든 작업은 실행 컨텍스트 내에서 이루어집니다. JS 코드가 실행될 때마다 전역 실행 컨텍스트가 생성됩니다.
전역 실행 컨텍스트는 코드가 실행되는 가장 바깥쪽 컨텍스트 또는 스코프 입니다. 자바스크립트 프로그램이 실행되면 자바스크립트 엔진은 전역 실행 컨텍스트를 생성하여 전역 범위를 관리합니다. 전역 실행 컨텍스트를 생성하는 동안에는 두 단계가 있습니다.
전역 실행 컨텍스트를 생성하는 동안 변수 및 함수 선언이 올라가고 메모리 공간이 할당되는 생성 단계가 발생합니다. 이 단계에서는 변수가 정의되지 않은 상태(undefined)로 초기화 됩니다. (호이스팅)
초기화 단계가 끝나면 실제 코드가 위에서 아래로 실행됩니다. 이 단계에서 할당과 함수 호출이 수행됩니다.
자바스크립트에서 호출 스택은 프로그램에서 함수 호출을 추적하는 데이터 구조입니다. 이는 마지막에 호출된 함수가 가장 먼저 제거되는 LIFO(Last In, First Out) 방식으로 작동합니다.
함수가 호출되면 새 프레임이 호출 스택에 푸시되고, 함수가 반환되면 해당 프레임이 스택에서 팝아웃됩니다.
function firstfunction() {
secondFunction();
console.log("Inside firstFunction");
}
function secondFunction() {
thirdFunction();
console.log("Inside secondFunction");
}
function thirdFunction() {
console.log("Inside thirdFunction");
}
firstFunction();
firstFuction()
이 호출됩니다.firstFunction()
이 호출되고나면 위 코드의 트리거 지점인 빈 호출스택으로 푸시됩니다.
firstFunction()
내부의 첫번째 줄은 secondFuction()
이고, 이 줄이 호출되면 secondFunction()
이 호출 스택으로 푸시되고, 그럼 호출 스택은 다음과 같아집니다.
secondFunction()
내에서 thirdFunction()
이 호출되고 호출 스택은 다음과 같이 됩니다.
thridFunction()
내부에서는 "console.log()" 문이 실행되고 그 이후에는 코드에 실행할 다른 내용이 없습니다. 따라서 함수는 거기서 끝나고 호출 스택의 맨 위에서 제거됩니다.
호출 스택은 firstFunction
-> secondFunction()
이 됩니다.
이제 secondFunction()
이 맨 위에 있고 호출 스택에 의해 실행되고 있으며, secondFunction()
내부의 "console.log()" 문이 실행됩니다. secondFunction()
에 대한 실행이 완료되면 호출 스택에서도 제거되고 호출 스택에는 firstFunction()
만 남게 됩니다.
firstFunction()
도 마찬가지이며 실행이 끝나면 호출스택에서 제거됩니다. 그리고 다시 호출 스택이 비워집니다.
자바스크립트에서 Task queue는 비동기 작업을 처리하는데 중요한 이벤트 루프 메커니즘의 일부입니다. 이벤트 루프는 메시지 큐에서 새 이벤트를 지속적으로 확인하고 순차적으로 실행하는 프로세스입니다. 태스크 큐는 이 이벤트 루프 내의 큐 중 하나입니다.
태스크 큐를 더 넓은 개념에 적용하는 방법은 다음과 같습니다.
호출 스택은 프로그램에서 현재 실행 중인 함수를 추적하는 데이터 구조입니다. 함수가 호출되면 스택으로 푸시되고 완료되면 스택에서 튕겨져 나옵니다.
네트워크 요청과 같은 비동기 작업을 자바스크립트에서 수행하면 브라우저의 웹 API가 자바스크립트 런타임 외부에서 이런 작업을 처리합니다. 이러한 작업이 완료되면 콜백 함수가 작업 태크스 큐에 배치됩니다.
태스크 큐는 이벤트 루프에서 처리할 콜백 함수 및 이벤트를 보관하는 큐입니다. 태스크 큐의 콜백은 호출 스택이 비워진 후에 처리됩니다. 이렇게 하면 오래 실행되는 동기식 작업이 다른 작업의 실행을 차단하지 않습니다. 이러한 작업에는 비동기 작업(예: setTimeout
, AJAX 요청, 이벤트 핸들러)의 콜백 뿐 아니라 다른 유형의 작업도 포함될 수 있습니다.
"잡 큐"는 호출 스택의 각 작업이 끝날 때 실행되는 작업을 포함하는 특정 유형의 대기열입니다. 마이크로 태스크에는 일반적으로 Promise(then
, catch
)의 콜백, Node.js의 process.nextTick
및 MutationObserver
API가 포함됩니다.
이러한 컴포넌트가 함께 동작하는 방식을 이해하면 코드가 비동기 작업을 효과적으로 처리할 수 있도록 하는 자바스크립트의 중요한 개념인 이벤트 루프를 이해하는데 도움이 됩니다.