[Javascript] 실행 컨텍스트와 이벤트 루프

우지끈·2024년 10월 25일

오늘 실행 컨텍스트(Execution Context), 이벤트 루프(Event Loop), 클로저(Closure)에 대해 배웠는데 전에 이미 한 번 포스팅했던 클로저는 제외하고 실행 컨텍스트와 이벤트 루프에 대한 글을 적어보려 한다.

실행 컨텍스트(Execution Context)

실행 컨텍스트는 자바스크립트 코드를 실행하는데 필요한 환경을 제공하는 객체이다. 자바스크립트 엔진이 코드를 평가하고 실행하는 데 필요한 정보를 담고 있는 공간이라 생각하면 된다.

코드는 실행 컨텍스트 단위로 2가지 단계를 거쳐서 동작한다.

  1. 평가 단계(컴파일 단계): 레코드 수집(호이스팅) 및 상위 스코프(Outer) 결정
  2. 실행 단계(런타임 단계): 함수 실행 시 실행 컨텍스트를 콜 스택에 푸쉬하고 변수에 값 할당

Record: 선언된 변수, 함수, 클래그 등이 기록되는 공간(식별자와 그 값이 저장됨)
Outer: 상위 스코프를 가리키고 있는 공간(스코프 체인 형성)

실행 컨텍스트는 총 3가지 요소로 구성되어 있다.

  1. Variable Environment(VE) : 평가 단계에서 호이스팅이 이루어진 공간 -> 이 단계에서 변수가 메모리에 할당
  2. Lexical Environment(LE) : 실행 단계에서 사용하는 공간 -> VE의 정보를 바탕으로 실행 단계에서 변수를 참조하고 값을 할당
  3. this binding

개념 이해를 위해 예시를 들어보자면 다음과 같다.

function outerFn(param) {
  let outerVar = "Outer변수";
  console.log(param);

  function innerFn() {
    console.log(outerVar);  // outerVar를 참조
  }

  innerFn();
}

outerFn(10);

해당 코드의 진행 과정을 그림으로 표현해보겠다.

우선 전역 실행 컨텍스트가 생성되며, record에outerFn함수가 호이스팅 된다.(참고로 함수 선언문의 경우 함수 전체가 호이스팅)
그 다음으로는 outerFn의 실행 컨텍스트가 생성되고, param, outerVar, innerFn이 호이스팅 된다.

paramouterVar가 각각 10Outer변수로 설정되었고, console.log(param)이 실행되어 콘솔에 10이 출력된다.

그 다음엔 innerFn이 호출되어 새로운 실행 컨텍스트가 콜스택에 추가된다. innerFnouterFn의 VE를 참조하는 클로저가 형성되며, outerVar를 참조하게 된다.

그리고 innerFn이 실행되며, console.log(outerVar)가 실행되어 "Outer변수가 콘솔에 출력된다.

innerFn의 실행이 완료되면서 콜스택에서 제거됐고, outerFn의 실행 재개 후 더 이상 실행할 코드가 없으므로 outerFn도 콜스택에서 제거된다.


이벤트 루프(Event Loop)

이벤트 루프란 비동기 작업을 관리하고 순서대로 프로그램의 실행 흐름을 제어하기 위한 장치이다.

주요 구성 요소는 다음과 같다.

  • 콜 스택: 자바스크립트 코드를 실행
  • 콜백 큐: 비동기 작업이 완료되면 콜백 함수가 대기하는 큐
  • Web APIs: 비동기 작업을 처리하는 브라우저의 API 모음 (ex. setTimeOut, fetch, addEventListner)
  • 태스크 큐(매크로태스크 큐): setTimeOut, setInterval, fetch, addEventListner 등의 콜백 함수가 들어가는 공간
  • 마이크로태스크 큐: promise.then, MutationObserver 등의 콜백 함수가 들어가는 공간
  • 이벤트 루프: 콜 스택과 콜백 큐를 모니터링하며, 콜 스택이 비어 있을 때 콜백 큐에서 대기 중인 콜백 함수를 가져와 실행하는 역할을 하는 개체

    마이크로태스크 큐가 태스크 큐보다 실행 우선순위가 높다!

console.log("1. Start");

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

Promise.resolve().then(() => {
  console.log("3. Microtask");
});

console.log("4. End");

예시 코드를 보면, 우선 동기 코드인 console.log("1. Start");console.log("4. End"); 가 출력될 것이다. 그 다음으로는 실행 우선 순위가 높은 마이크로태스크 큐에 들어가게 되는 promise.then 함수를 통해 console.log("3. Microtask"); 가 출력될 것이고, 마지막으로 console.log("2. Timeout callback"); 부분이 출력될 것이다.

따라서 동기 코드와 비동기 코드가 같은 스코프에 있을 때, 모든 동기 코드가 실행된 이후에 이벤트 루프에 의해 비동기 코드가 우선 순위에 따라 순차적으로 실행된다는 것을 알 수 있다.

0개의 댓글