실행 컨텍스트(Execution Context)란?
자바스크립트 코드를 실행하는 데 필요한 환경 정보(변수, 함수 선언, 스코프, this 등)를 담은 논리적 객체이다.
코드가 실행될 때 생성되어 스택(Stack) 구조로 관리되며, 호이스팅, 클로저, 스코프 체인 등의 동작 원리를 정의하는 핵심 개념이다.
라고 정의하면 어렵게 느껴질 수 있다.
이해하기 쉽게 단순한 예를 들자면,
친구가 나에게 “밥 먹었어?” 라고 묻는 상황을 가정해보자.
이 “밥 먹었어?” 는 앞뒤 문맥에 따라 의미가 달라진다.
같이 밥을 먹자는 의미일 수도 있고, 단순한 안부를 묻는 것일 수도 있고, 배고프다는 뜻일 수도 있다.
정리하자면 컨텍스트는 지금의 상황, 문맥, 배경을 말하는데 이 말이 왜 이렇게 들리는지를 설명해주는 환경이다.
위의 개념에 빗대어 설명하자면, 자바스크립트 실행 컨텍스트는
자바스크립트 코드가 실행되기 위해 필요한 모든 환경 정보를 모아놓은 가상의 상자이다.
상자(컨텍스트)가 어떻게 관리되는지, 상자 내부가 어떻게 구성되어 있는지 살펴보자.
1. 실행 컨텍스트의 관리: 콜 스택
자바스크립트 엔진은 코드를 실행할 때 필요한 컨텍스트들을 콜 스택이라는 바구니에 담아 관리한다.
함수가 호출될 때 마다 새로운 컨텍스트 상자가 만들어져 스택에 쌓이고(push), 함수 실행이 끝나면 상자가 스택에서 제거(pop) 된다.
2. 실행 컨텍스트의 내부 구성
실행 컨텍스트는 크게 두 개의 환경을 가지고 있다.
- 렉시컬 환경(Lexical Environment): 초기에는 변수 환경과 같지만, 코드가 실행되면서 실시간으로 변경되는 정보를 담는다. 호이스팅, 스코프 체인, 클로저는 모두 이 렉시컬 환경 덕분에 작동한다.
- 변수 환경(Variable Environment): 실행 컨텍스트가 생성될 때의 스냅샷이다. 코드 실행 중에 변경되지 않는다.
실행 컨텍스트를 이야기할 때는 이 중 Lexical Environment를 중심으로 설명한다. 이 렉시컬 환경은 다시 두 가지 핵심 구성 요소로 나뉜다.
- Environment Record (환경 레코드): 현재 컨텍스트 내의 식별자 정보들을 저장하는 곳
- 함수 선언문, var 변수, let/const 변수 등이 여기에 등록된다.
- 호이스팅(Hoisting): 자바스크립트 엔진은 코드를 실행하기 전, 실행 컨텍스트를 생성하는 단계에서 이 '환경 레코드'에 현재 스코프의 모든 식별자를 미리 등록해 둔다. 그래서 변수 선언 코드가 아래에 있어도 위에서 참조할 수 있는 것이다.
- Outer Environment Reference (외부 환경 참조): 상위스코프를 가리킨다. 외부 렉시컬환경에 대한 참조를 통해 단방향 링크드 리스트인 스코프 체인을 구성한다.
- 현재 컨텍스트가 어디서 선언되었는지에 대한 정보를 가지고 있다.
- 이 참조를 통해 자바스크립트 엔진은 현재 컨텍스트에서 변수를 찾지 못하면 상위 컨텍스트의 환경 레코드를 검색할 수 있다.
소스코드의 평가와 실행 과정
var x = 10;
function foo(){
console.log("foo");
}
function bar(){
console.log("bar");
}
foo();
bar();

- 전역 실행 컨텍스트 생성 (평가 단계)코드를 실행하기 위한 준비 단계.
- 렉시컬 환경 구성:
- 환경 레코드: 변수와 함수 선언을 메모리에 등록한다.
- x : var로 선언되었으므로 메모리에 공간을 확보하고 undefined 로 초기화한다.
- foo() : 함수 전체를 메모리에 통째로 등록한다.
- bar() : 함수 전체를 메모리에 통째로 등록한다.
- 환경 레코드: 변수와 함수 선언을 메모리에 등록한다.
- 렉시컬 환경 구성:
- 코드를 한 줄씩 실행하기 전, 선언문들을 찾아내어 실행 컨텍스트를 준비하는 단계입니다.
- 전역 코드 실행 (실행 단계)
- var x = 10; : undefined 였던 x 값에 10을 할당한다.
- foo() 호출 : 함수 foo()를 호출한다. 이때 새로운 함수 실행 컨텍스트가 생성된다.
- 이제 위에서 아래로 코드를 한 줄 씩 실행한다.
- foo() 함수 실행
- foo() 실행 컨텍스트 생성:
- 내부에 선언된 변수가 없으므로 환경 레코드는 비어있다.
- 외부 환경 참조는 전역 컨텍스트를 가리킨다.
- 실행: console.log(”foo”)를 실행하여 콘솔에 “foo” 를 출력한다.
- 종료: 실행이 끝나면 foo 실행 컨텍스트는 콜 스택에서 제거(pop)된다. 제어권은 다시 전역 실행 컨텍스트로 돌아간다.
- foo() 실행 컨텍스트 생성:
- foo() 함수가 호출되면 제어권이 함수 내부로 넘어가며 콜 스택에 foo 컨텍스트가 쌓인다.
- bar() 함수 실행
- foo() 와 똑같은 과정을 거친다.
7. 최종 상태
모든 코드가 실행되었다.
전역에 더 실행할 코드가 없다면 전역 실행 컨텍스트도 종료되거나, 브라우저 환경이라면 페이지가 닫힐 때까지 유지된다.
클로저(closure)란?
클로저는 함수가 선언될 당시의 렉시컬 환경(Lexical Environment)을 기억하여, 함수가 외부 스코프 밖에서 실행될 때도 그 환경에 접근할 수 있는 힘을 말한다.
- 실행 컨텍스트로 보는 클로저의 원리
function outer() {
const x = "x";
return function inner() {
console.log(x);
};
}
const myFunc = outer();
myFunc();
- outer() 호출: outer 실행 컨텍스트가 콜 스택에 쌓인다. 환경 레코드에 x = "x"가 저장된다.
- outer() 종료: outer 함수가 inner 함수를 반환하고 끝난다. 원칙적으로는 outer 컨텍스트가 콜 스택에서 제거(Pop)되어 그 안의 x 변수도 사라져야 한다.
- 반전: 하지만 반환된 inner 함수(현재 myFunc)가 x이라는 변수를 참조하고 있다.
- 클로저의 등장: 자바스크립트 엔진은 "누군가 이 환경을 아직 참조하고 있네?"라고 판단하여 outer의 렉시컬 환경을 메모리에서 지우지 않고 남겨둔다.
- 결론: outer 컨텍스트 상자는 콜 스택에서 빠졌지만, 그 안의 렉시컬 환경(데이터들)은 메모리 어딘가에 살아남아 inner 함수와 연결되어 있는 상태, 이것이 클로저다.
클로저가 왜 필요할까? (핵심 용도)
가장 큰 이유는 상태를 안전하게 은닉하기 위해서다.
- 변수 보호 (Private 변수) 아무나 수정할 수 없지만, 특정 함수를 통해서만 접근할 수 있는 변수를 만들 때 사용한다.
function createCounter() {
let count = 0;
return {
increase: function() { count++; return count; },
decrease: function() { count--; return count; }
};
}
const counter = createCounter();
console.log(counter.increase()); // 1
console.log(counter.increase()); // 2
이 예제에서 count 변수는 createCounter의 실행이 끝난 뒤에도 클로저 덕분에 increase, decrease 함수에 의해 계속 살아있게 된다.
클로저의 특징 요약
- 기억력: 자신이 태어난 곳(상위 스코프)의 환경을 끝까지 기억한다.
- 캡슐화: 외부로부터 데이터를 숨기고 오직 정해진 메소드를 통해서만 상태를 변경하게 강제할 수 있다.
- 메모리: 참조가 살아있는 한 메모리를 차지하므로, 너무 남발하면 성능에 영향을 줄 수 있다. (필요 없어지면 null을 할당해 참조를 끊어주는 것이 좋다.)
- 실행 컨텍스트가 "상자가 쌓이고 빠지는 물리적인 흐름"이라면, 클로저는 그 흐름 속에서 "필요한 기억을 유지하는 끈"이라고 볼 수 있다.
'프론트엔드 > Language' 카테고리의 다른 글
| [TypeScript] 제너릭, Type Predicate, 함수 오버로드, async/await (0) | 2026.03.31 |
|---|---|
| [JavaScript] 스코프(Scope) (0) | 2026.03.09 |
