끄적끄적 코딩
article thumbnail
728x90

서론

자바스크립트는 프론트엔드 개발에서 가장 핵심적인 언어입니다. 하지만 자바스크립트가 어떻게 코드를 실행하고 처리하는지에 대해 정확히 이해하지 못하면, 비동기 처리나 렌더링 문제 등을 만날 때 어려움을 겪게 됩니다. 이번 글에서는 자바스크립트의 동작 과정을 실제 코드 실행 흐름을 따라가며 설명하고, 그 과정 속에서 실행 컨텍스트, 렉시컬 환경, 이벤트 루프, 인터프리터, 클로저, 콜 스택 등 주요 개념을 자연스럽게 이해해보겠습니다.

https://infodocbib.net/2020/12/javascript/


자바스크립트 코드 실행

자바스크립트 코드는 자바스크립트 엔진에서 실행됩니다. 대표적으로 크롬의 V8, 파이어폭스의 SpiderMonkey, 사파리의 JavaScriptCore 등이 있습니다. 엔진 내부에서는 인터프리터가 코드를 한 줄씩 실행하거나, 자주 실행되는 코드를 JIT 컴파일러가 최적화해 실행 성능을 높입니다.

https://dev.to/mihirverma7781/inside-javascript-engine-373

자바스크립트는 엔진만으로 완전한 실행이 불가능하며, 브라우저나 Node.js가 제공하는 런타임 환경이 함께 필요합니다. 이 런타임은 메모리 힙콜 스택, 비동기 작업을 처리하는 Web API, 그리고 작업 흐름을 조율하는 이벤트 루프태스크 큐 등으로 구성되어 있습니다.


코드 실행 흐름

코드가 실행되면 가장 먼저 실행 컨텍스트가 생성됩니다. 실행 컨텍스트는 자바스크립트 코드가 실행되는 동안 필요한 정보를 담고 있는 환경이며, 일종의 실행 단위입니다. 이 안에는 변수, 함수 선언, this 값, 렉시컬 환경 등이 포함됩니다.

https://velog.io/@design0728/%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8Execution-Context

자바스크립트는 프로그램이 시작될 때 전역 실행 컨텍스트를 먼저 생성하고, 이후 함수가 호출될 때마다 새로운 함수 실행 컨텍스트를 생성합니다. 각 실행 컨텍스트는 콜 스택에 순서대로 쌓이며, 가장 위에 있는 실행 컨텍스트만이 현재 실행 중인 상태가 됩니다.

예를 들어 함수를 호출하면, 해당 함수의 실행 컨텍스트가 스택에 추가되고, 실행이 완료되면 스택에서 제거됩니다. 이런 스택 구조 덕분에 자바스크립트는 현재 어떤 코드가 실행 중이며, 어떤 함수가 어떤 순서로 호출되었는지를 정확하게 관리할 수 있습니다.

https://haileychoi15.medium.com/%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-execution-context-%EC%99%80-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85-hoisting-3f407ad4820e


변수의 저장과 참조 방식

각 실행 컨텍스트 내부에는 렉시컬 환경이 존재합니다. 렉시컬 환경은 현재 컨텍스트에서 정의된 변수, 함수 선언, 매개변수 등을 저장하고, 상위 스코프에 대한 참조도 함께 포함하는 객체입니다. 이 환경은 코드가 작성된 위치에 따라 결정되며, 이를 렉시컬 스코프 또는 정적 스코프라고 부릅니다.

https://www.yalco.kr/@javascript-abyss/12-1/

예를 들어 함수가 어디서 실행되었는지가 아니라, 어디서 정의되었는지에 따라 접근할 수 있는 변수의 범위가 정해집니다. 이 구조는 함수가 자신이 정의된 위치를 기준으로 외부 스코프의 변수에 접근할 수 있도록 합니다.

이러한 특징 덕분에 자바스크립트에서는 클로저가 작동할 수 있습니다. 클로저는 함수가 생성될 당시의 렉시컬 환경을 기억하여, 나중에 해당 함수가 호출되더라도 외부 변수에 계속 접근할 수 있게 해줍니다. 이로 인해 비동기 처리나 상태 관리, 정보 은닉 등 다양한 패턴에서 클로저가 유용하게 활용됩니다.

// 클로저는 outer 함수의 실행이 끝났어도 inner 함수가 count에 접근할 수 있게 해줍니다.

function outer() {
  let count = 0;
  return function inner() {
    count++;
    console.log(count);
  }
}
const counter = outer();
counter(); // 1
counter(); // 2

비동기 작업 처리 방식

자바스크립트는 싱글 스레드 언어입니다. 즉, 한 번에 하나의 작업만 처리할 수 있기 때문에, 오래 걸리는 작업이 있다면 전체 흐름이 멈춰버릴 수 있습니다. 이 문제를 해결하기 위해 자바스크립트는 비동기 처리를 런타임 차원에서 지원하며, 이때 중요한 역할을 하는 것이 WEB API, 이벤트 루프 태스크 큐, 마이크로태스크 큐 등입니다.

브라우저는 DOM 처리, 타이머, Ajax 요청, 이벤트 리스너 등과 같은 기능을 자바스크립트 엔진 외부의 WEB API 환경에서 처리합니다. 이러한 작업들은 백그라운드에서 실행되고, 완료되면 콜백 함수를 큐에 등록합니다.

예를 들어, setTimeout은 일정 시간이 지난 뒤 실행할 콜백을 등록하는 함수입니다. 브라우저는 타이머가 끝나면 해당 콜백을 태스크 큐(Task Queue) 에 넣고, 이벤트 루프(Event Loop) 는 콜 스택이 비었는지 확인한 후, 큐에 있는 콜백을 스택에 올려 실행합니다.

https://velog.io/@ahsy92/%EA%B8%B0%EC%88%A0%EB%A9%B4%EC%A0%91-JavaScript-%EB%9F%B0%ED%83%80%EC%9E%84-%EC%9E%91%EB%8F%99%EB%B0%A9%EC%8B%9D-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%99%80-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84

반면, Promise.then, MutationObserver 와 같은 작업은 마이크로태스크 큐에 들어가며, 이는 태스크 큐보다 우선적으로 처리됩니다. 이벤트 루프는 한 번의 루프마다 마이크로태스크 큐를 모두 비운 뒤 태스크 큐를 처리합니다.

https://www.linkedin.com/pulse/javascript-why-do-some-tasks-go-microtask-queue-callback-hasan

이런 구조 덕분에 자바스크립트는 싱글 스레드임에도 동시에 여러 작업이 이뤄지는 것처럼 보이게 할 수 있으며, UI를 멈추지 않고 사용자 경험을 향상시킬 수 있습니다.


마무리

자바스크립트의 실행 흐름을 중심으로 실행 컨텍스트, 렉시컬 환경, 클로저, 이벤트 루프 같은 개념들을 차례대로 정리해봤습니다. 처음엔 각각 따로 이해하려 했던 개념들이, 흐름 안에서 연결되기 시작하니까 훨씬 명확하게 다가왔습니다.

이런 흐름을 이해하고 나면, 코드의 동작을 보다 명확히 예측할 수 있고, 디버깅 과정에서도 훨씬 수월하게 원인을 파악할 수 있습니다. 개인적으로도 자바스크립트를 다루는 방식에 도움이 되었고, 정리해두면 좋겠다는 생각에 이렇게 글로 남기게 되었습니다.

검색 태그