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

jiny·2025년 1월 28일

기술 면접

목록 보기
43/78

🗣️ 이벤트 루프와 실행 컨텍스트에 대해서 설명해주세요.

  • 의도: 자바스크립트의 기본 원리와 함께 기술 면접 대표 질문에 대해 준비해왔는지 확인하는 질문

    • 그림으로 설명하는 게 더 좋은 내용이라 제스처를 넣어 설명해도 좋다.
    • 스스로 대답할 때도 그림을 설명하듯이 상상해서 말한다.
  • 나의 답안

    실행 컨텍스트자바스크립트 코드가 실행되는 환경 정보 묶음이라고 할 수 있습니다.
    함수가 호출될 때마다 새로운 실행 컨텍스트가 생성되고,
    그 안에는 변수 환경(Variable Environment), 렉시컬 환경(Lexical Environment), 그리고 this 바인딩 정보가 포함됩니다.
    이 실행 컨텍스트들은 스택(Stack) 구조로 관리되어, 가장 최근에 호출된 함수가 먼저 실행되고, 끝나면 스택에서 제거됩니다.

    그런데 자바스크립트는 싱글 스레드 기반이기 때문에,
    네트워크 요청이나 타이머 등 오랜 시간이 걸리는 작업을 모두 한 스레드에서 처리하면 다른 코드가 멈춰버릴 수 있습니다.
    이 문제를 해결하기 위해 이벤트 루프(Event Loop)태스크 큐(Task Queue)가 동작합니다.

    이벤트 루프의 역할은 콜 스택이 비는 순간, 대기 중인 콜백 함수를 태스크 큐에서 꺼내 실행시키는 것입니다.
    즉, 비동기 작업들Web API 영역에서 수행되고,
    완료된 콜백이 태스크 큐에 들어가면 이벤트 루프가 스택이 비었을 때 하나씩 처리합니다.
    이 덕분에 자바스크립트는 싱글 스레드임에도 불구하고 비동기적으로 동작할 수 있습니다.

  • 주어진 답안 (모범 답안)

    우선 이벤트 루프는 그 이름답게 이벤트를 순서대로 돌아가며 처리하는 역할을 합니다.
    마치 줄을 서 있는 것처럼 이벤트 루프는 큐에 담긴 이벤트를 하나하나 호출 스택으로 옮깁니다.
    물론 실행 중인 컨텍스트가 없어 호출 스택이 비어있을 때만 말입니다.

    그리고 실행 컨텍스트는 이러한 이벤트가 실행되는 환경을 뜻합니다.
    앞서 말한 호출 스택에 이러한 실행 컨텍스트를 쌓아두고 작업이 종료되면 스택에서 빠져나오게 됩니다.

    이러한 이벤트 루프와 실행 컨텍스트는 싱글 스레드 환경인 자바스크립트에서 비동기 작업을 효과적으로 처리하기 위해 필수적인 메커니즘입니다.


📝 개념 정리

자바스크립트의 이벤트 루프실행 컨텍스트는 자바스크립트 엔진의 핵심 동작 방식과 관련된 중요한 개념이다.
이를 이해하면 비동기 코드의 동작 방식과 콜백 처리, async/await, Promise 등이 작동하는 원리를 더 명확히 알 수 있다.

🌟 실행 컨텍스트 (Execution Context)

실행 컨텍스트는 자바스크립트 코드가 실행되는 환경이다.
자바스크립트 엔진은 코드를 실행할 때 실행 컨텍스트를 생성하고, 이 컨텍스트가 함수 호출, 변수, 스코프, 그리고 this와 같은 정보를 관리한다.

  1. 실행 컨텍스트의 구성
    1) Variable Environment (변수 환경)
    • 선언된 변수와 함수 선언, let, const, var로 선언된 변수들이 저장되는 곳이다.
    • 초기화 시점과 값의 상태가 여기에 저장된다.
    2) Lexical Environment (렉시컬 환경)
    • 현재 컨텍스트의 스코프 정보를 나타낸다.
    • 상위 스코프와의 참조(chain)을 포함한다.
    • 클로저가 동작하는 핵심 원리도 여기에 포함된다.
    3) This Binding
    • 현재 컨텍스트에서 this가 무엇을 가리키는지에 대한 정보가 저장된다.
  1. 실행 컨텍스트 생성 단계
    1) Creation Phase (생성 단계)
    • 변수 선언, 함수 선언, this 바인딩이 설정된다.
    • 변수는 초기화되지 않은 상태에서 메모리에 등록된다.
    2) Execution Phase (실행 단계)
    • 코드가 한 줄씩 실행되면서 변수에 값이 할당되고, 함수가 호출된다.
  1. 실행 컨텍스트의 종류
    • 전역 실행 컨텍스트: 자바스크립트 코드가 처음 실행될 때 생성되는 컨텍스트로, 전역 객체(window 또는 global)를 포함한다.
    • 함수 실행 컨텍스트: 함수가 호출될 때마다 생성된다.
    • Eval 실행 컨텍스트: eval() 함수 내부에서 코드 실행 시 생성된다. (잘 사용되지 않음)
  1. 실행 컨텍스트와 콜 스택의 관계

    • 실행 컨텍스트는 콜 스택(Call Stack)에 쌓인다.

    • 함수가 호출될 때마다 실행 컨텍스트가 생성되고, 실행이 끝나면 해당 컨텍스트는 스택에서 제거된다.

    • 예시

      function first() {
        console.log("First");
        second();
      }
      function second() {
        console.log("Second");
      }
      
      first();
      • 실행 순서
        1. 처음 실행: 전역 컨텍스트 생성
          • 코드 실행 시작 시 전역 실행 컨텍스트가 콜 스택에 쌓인다.
        2. first() 함수 호출
          • first() 실행 컨텍스트가 콜 스택에 추가된다.
          • console.log("First")가 실행된다.
        3. second() 함수 호출
          • second() 실행 컨텍스트가 콜 스택에 추가된다.
          • console.log("Second")가 실행된다.
        4. second() 종료
          • second() 컨텍스트가 콜 스택에서 제거된다.
        5. first() 종료
          • first() 컨텍스트가 콜 스택에서 제거된다.
      • 결과
        First
        Second
  1. 렉시컬 환경(Lexical Environment)의 중요성
    렉시컬 환경은 코드 작성 시점(즉, 함수가 어디에서 정의되었는지)에 따라 상위 스코프를 결정한다. 이는 스코프 체인의 원리가 된다.
    function outer() {
      let x = 10;
      function inner() {
        console.log(x); // 10
      }
      inner();
    }
    outer();
    • 렉시컬 환경 덕분에 inner 함수는 outer 함수의 변수(x)에 접근할 수 있다.
    • 이는 클로저(closure)가 동작하는 기반 원리이다.

🌟 이벤트 루프 (Event Loop)

자바스크립트는 싱글 스레드(Single Thread) 언어이다. 즉, 한 번에 하나의 작업만 실행된다.
하지만, 비동기 작업(예: setTimeout, 네트워크 요청, 파일 읽기 등)을 처리하기 위해 이벤트 루프라는 메커니즘을 사용한다.
이벤트 루프는 콜 스택(Call Stack), 태스크 큐(Task Queue), 그리고 마이크로태스크 큐(Microtask Queue)를 관리하며 자바스크립트 코드의 실행 순서를 결정한다.

  1. 이벤트 루프의 동작 원리
    1) 콜 스택 (Call Stack)
    • 자바스크립트 코드 실행의 스택이다.
    • 함수 호출 시 스택에 추가(push), 실행이 끝나면 제거(pop)된다.
    2) 태스크 큐 (Task Queue)
    • 비동기 작업의 콜백(예: setTimeout)이 완료되면, 해당 콜백이 태스크 큐에 들어간다.
    3) 마이크로태스크 큐 (Microtask Queue)
    • Promise.then, MutationObserver 등 마이크로태스크는 별도의 우선순위 높은 큐에 저장된다.
    • 마이크로태스크는 태스크 큐보다 먼저 처리된다.
    4) 이벤트 루프의 역할
    • 이벤트 루프는 콜 스택이 비어있는지를 확인하고, 비어 있으면 태스크 큐 또는 마이크로태스크 큐에서 작업을 꺼내 실행한다.
  1. 이벤트 루프 동작 예시

    console.log("Start");
    
    setTimeout(() => {
      console.log("Timeout");
    }, 0);
    
    Promise.resolve().then(() => {
      console.log("Promise");
    });
    
    console.log("End");
    • 실행 순서
      1. "Start"console.log("Start")는 바로 실행 (콜 스택에 들어갔다가 바로 제거)
      2. "End"console.log("End")도 즉시 실행
      3. Promise.then()마이크로태스크 큐에 들어가므로 "Promise"가 먼저 출력
      4. setTimeout태스크 큐에 들어가므로 "Timeout"이 마지막에 실행
    • 출력 결과
      Start
      End
      Promise
      Timeout

🌟 실행 컨텍스트와 이벤트 루프의 관계

  • 실행 컨텍스트는 콜 스택의 동작을 담당한다.
  • 이벤트 루프는 콜 스택이 비어있는지 확인하며, 태스크 큐와 마이크로태스크 큐를 관리하여 비동기 작업을 처리한다.

🌟 싱글 스레드와 논블로킹 I/O

자바스크립트는 싱글 스레드로 동작하지만, 논블로킹 I/O를 통해 비동기 작업을 처리한다.
이 비동기 처리는 브라우저나 Node.js의 백그라운드 스레드에서 실행된다.

  • 브라우저에서의 비동기 작업 처리

    setTimeout(() => console.log("Timeout"), 1000);
    
    fetch("https://api.example.com").then(() => console.log("Fetch done"));
    
    console.log("Sync task");
    1. setTimeout: 브라우저의 타이머 API가 처리한다.
    2. fetch: 브라우저의 네트워크 스레드가 요청을 처리한다.
    3. console.log(동기 코드): 즉시 실행된다.

🌟 async/await와 이벤트 루프

async/await는 비동기 코드를 동기 코드처럼 보이게 작성할 수 있게 한다.
하지만 내부적으로는 Promise를 사용하며, 결국 마이크로태스크 큐를 활용한다.

async function example() {
  console.log("Start");
  await new Promise(resolve => setTimeout(resolve, 1000));
  console.log("End");
}
example();
console.log("Outside");
  • 실행 순서
    1. "Start"가 출력된다.
    2. await으로 인해 Promise가 해결될 때까지 기다린다. → 이 시점에 함수 실행이 중단된다.
    3. "Outside"가 출력된다. → 이벤트 루프가 계속 실행된다.
    4. 1초 후 Promise가 해결되면 "End"가 출력된다.
  • 출력 결과
    Start
    Outside
    End

🌟 클로저와 이벤트 루프의 상호작용

function counter() {
  let count = 0;
  return function increment() {
    count++;
    console.log(count);
  };
}

const inc = counter();
setTimeout(inc, 1000); // 1
setTimeout(inc, 2000); // 2
  • 클로저는 counter 함수가 종료된 후에도 count 변수를 기억한다.
  • 이벤트 루프를 통해 setTimeout이 처리되면서 클로저가 작동한다.

0개의 댓글