2025-09-22 23:55
-
자바스크립트 실행 컨텍스트는 코드가 실행되기 위해 필요한 환경 정보를 담은 객체로, 엔진의 핵심 동작 원리이다.
-
모든 코드는 실행 컨텍스트 내에서 평가되고 실행되며, 이 컨텍스트는 콜 스택이라는 자료 구조로 관리된다.
-
실행 컨텍스트를 이해하면 호이스팅, 스코프 체인, 클로저와 같은 자바스크립트의 중요 개념을 명확하게 파악할 수 있다.
자바스크립트 실행 컨텍스트 완벽 정복 핸드북 엔진의 비밀을 파헤치다
자바스크립트 개발자라면 누구나 ‘호이스팅’, ‘스코프’, ‘클로저’와 같은 단어를 들어보았을 것이다. 이 개념들은 때로는 우리를 혼란스럽게 만들고, 예상치 못한 버그의 원인이 되기도 한다. 하지만 이 모든 현상을 관통하는 하나의 핵심 원리가 있다면 어떨까? 바로 **실행 컨텍스트(Execution Context)**다.
실행 컨텍스트는 자바스크립트 엔진이 코드를 실행하기 위해 필요한 모든 정보를 담고 있는 거대한 ‘설명서’ 또는 ‘작업 지시서’와 같다. 우리가 작성한 코드가 어떻게 변수를 찾고, 함수를 호출하며, this가 누구를 가리키는지 결정하는 모든 과정이 이 실행 컨텍스트 안에서 이루어진다.
이 핸드북은 자바스크립트의 심장과도 같은 실행 컨텍스트의 모든 것을 파헤친다. 왜 만들어졌는지부터 시작해, 어떤 구조로 이루어져 있는지, 그리고 어떻게 우리의 코드를 생명력 있는 존재로 만드는지 그 여정을 함께 따라가 보자. 이 글을 끝까지 읽는다면, 당신은 자바스크립트 코드를 보는 새로운 눈을 갖게 될 것이다.
1. 실행 컨텍스트는 왜 만들어졌을까 코드 실행의 무대
요리사가 요리를 시작하기 전에 무엇을 할까? 필요한 재료를 모두 꺼내고, 조리 도구를 정리하며, 레시피를 다시 한번 확인하는 ‘미장플라스(Mise en place)’ 과정을 거친다. 이 준비 과정이 없다면 요리는 뒤죽박죽이 될 것이다.
자바스크립트 엔진도 마찬가지다. 코드를 한 줄 한 줄 실행하기 전에, 코드를 실행할 ‘환경’을 완벽하게 준비해야 한다. 이 환경에는 다음과 같은 정보들이 필요하다.
-
지금 실행할 코드 범위 안에 어떤 변수와 함수들이 선언되어 있는가?
-
상위 스코프(바깥쪽 코드)에는 어떤 변수들이 있는가?
-
this키워드는 무엇을 가리켜야 하는가?
이 모든 질문에 대한 답을 담고 있는 객체가 바로 실행 컨텍스트다. 즉, 실행 컨텍스트는 코드가 실행될 때 필요한 환경 정보들을 모아놓은 객체이며, 코드가 실행되기 위한 일종의 ‘무대 장치’라고 할 수 있다. 이 무대 장치가 설치되어야만 자바스크립트 배우들(변수, 함수 등)이 자신의 역할을 제대로 수행할 수 있다.
자바스크립트에는 크게 세 종류의 실행 컨텍스트가 존재한다.
-
전역 실행 컨텍스트 (Global Execution Context): 자바스크립트 파일이 처음 실행될 때 생성되는 가장 기본적인 컨텍스트. 웹 브라우저 환경에서는
window객체를, Node.js 환경에서는global객체를this로 가진다. 이 컨텍스트는 애플리케이션이 종료될 때까지 유지된다. -
함수 실행 컨텍스트 (Function Execution Context): 함수가 호출될 때마다 해당 함수를 위한 새로운 컨텍스트가 생성된다. 각 함수는 자신만의 실행 컨텍스트를 가지며, 이는 함수의 지역 변수, 매개변수,
arguments객체 등의 정보를 관리한다. -
Eval 실행 컨텍스트 (Eval Execution Context):
eval()함수를 사용할 때 생성되는 특수한 컨텍스트. 보안 및 성능 이슈로 사용을 권장하지 않으므로, 이 핸드북에서는 깊게 다루지 않는다.
2. 실행 컨텍스트의 해부학 구조와 구성 요소
실행 컨텍스트라는 거대한 객체 안에는 무엇이 들어있을까? ECMAScript 사양에 따르면 실행 컨텍스트는 크게 세 가지 핵심 요소로 구성된다.
| 구성 요소 | 역할 | 주요 특징 |
|---|---|---|
| 렉시컬 환경 (Lexical Environment) | 현재 스코프의 변수, 함수 선언 및 외부 스코프 참조를 저장 | 코드 실행 중 변경 사항이 실시간으로 반영 |
| 변수 환경 (Variable Environment) | 렉시컬 환경과 유사하나, var로 선언된 변수만 관리 | 생성 시점의 스냅샷을 유지, 호이스팅과 관련 깊음 |
| this 바인딩 (ThisBinding) | this 키워드가 참조할 객체를 결정 | 함수 호출 방식에 따라 동적으로 결정됨 |
2.1. 렉시컬 환경 (Lexical Environment) 코드의 지도
‘Lexical’이라는 단어는 ‘어휘의’, ‘사전적인’이라는 의미를 가지며, 코드의 **물리적인 위치(작성된 위치)**와 관련이 깊다. 렉시컬 환경은 코드가 작성된 그 위치를 기반으로 스코프를 결정하고, 변수와 함수를 관리하는 핵심적인 역할을 한다. 렉시컬 환경은 다시 두 부분으로 나뉜다.
1) 환경 레코드 (Environment Record)
현재 스코프 내의 식별자(변수, 함수 이름)와 그 결과값(실제 데이터)을 키-값 형태로 기록하고 저장하는 공간이다. 마치 동네 전화번호부처럼, “이름(key)을 대면 정보(value)를 알려주는” 역할을 한다.
-
선언적 환경 레코드 (Declarative Environment Record):
function,let,const등으로 선언된 변수와 함수를 저장한다. -
객체 환경 레코드 (Object Environment Record): 전역 실행 컨텍스트에서
var로 선언된 변수나 함수를 전역 객체(e.g.,window)의 프로퍼티로 바인딩하여 관리한다.
let과 const로 선언된 변수는 환경 레코드에 기록은 되지만, 해당 선언문에 도달하기 전까지는 접근할 수 없는 **일시적 사각지대(Temporal Dead Zone, TDZ)**에 놓이게 된다. 이것이 let과 const가 var와 달리 호이스팅되지 않는 것처럼 보이는 이유다.
2) 외부 환경 참조 (Outer Environment Reference)
현재 실행 컨텍스트의 바깥쪽 렉시컬 환경을 가리키는 포인터다. 만약 현재 스코프에서 변수를 찾지 못하면, 자바스크립트 엔진은 이 외부 환경 참조를 따라 바깥쪽 스코프로 이동하여 변수를 계속해서 찾아 나간다. 이 연결고리가 계속 이어져 있는 것을 **스코프 체인(Scope Chain)**이라고 부른다.
만약 바깥쪽 스코프가 없다면 (전역 렉시컬 환경의 경우), 외부 환경 참조 값은 null이 된다.
2.2. 변수 환경 (Variable Environment) 최초의 스냅샷
변수 환경은 기본적으로 렉시컬 환경과 동일한 구조를 가지고 있다. 하지만 한 가지 중요한 차이점이 있다. 변수 환경은 오직 var로 선언된 변수만을 위한 정보를 담는다는 것이다.
실행 컨텍스트가 생성될 때, 변수 환경은 var로 선언된 변수들을 수집하여 undefined로 초기화한다. 이것이 바로 var 변수 호이스팅의 실체다. 일단 스냅샷처럼 초기 상태를 기록하고 나면, 렉시컬 환경이 코드 실행 과정에서 발생하는 변화를 계속 추적한다.
이처럼 var와 let/const의 동작 방식 차이를 명확히 구분하기 위해 ECMAScript 사양에서는 두 환경을 분리하여 정의하고 있다.
2.3. this 바인딩 (ThisBinding) 실행의 주인공
this는 함수가 호출되는 방식에 따라 그 값이 동적으로 결정되는 특별한 키워드다. 실행 컨텍스트는 생성될 때 this가 무엇을 가리킬지 결정하여 ThisBinding 컴포넌트에 기록해 둔다.
-
전역 공간:
window또는global객체 -
일반 함수 호출:
window또는global객체 (엄격 모드에서는undefined) -
메서드 호출: 메서드를 호출한 객체 (예:
myObject.myMethod()에서this는myObject) -
생성자 함수 호출: 새로 생성되는 인스턴스
-
화살표 함수: 자신을 감싸고 있는 상위 스코프의
this를 그대로 물려받음 (Lexicalthis)
3. 실행 컨텍스트의 여정 콜 스택과 생명 주기
자바스크립트 엔진은 코드를 어떻게 순서대로 실행할까? 바로 **콜 스택(Call Stack)**이라는 자료 구조를 사용한다. 콜 스택은 현재 실행 중인 실행 컨텍스트들을 관리하는 LIFO(Last-In, First-Out) 방식의 스택이다.
비유하자면, 콜 스택은 접시를 쌓아놓는 것과 같다. 가장 나중에 쌓은 접시를 가장 먼저 꺼내는 것처럼, 가장 나중에 실행된 함수의 실행 컨텍스트가 가장 먼저 처리된다.
실행 컨텍스트의 생명 주기는 크게 생성 단계와 실행 단계로 나눌 수 있다.
3.1. 생성 단계 (Creation Phase)
함수가 호출되면, 자바스크립트 엔진은 코드를 실행하기 전에 먼저 해당 함수의 실행 컨텍스트를 생성한다. 이 단계에서 일어나는 일들은 다음과 같다.
-
렉시컬 환경 생성: 환경 레코드와 외부 환경 참조를 결정한다.
-
환경 레코드 생성: 함수 내의 매개변수,
arguments객체, 변수, 함수 선언 등을 스캔하여 환경 레코드에 기록한다. 이 과정에서let과const는 선언만 기록되고,var는undefined로 초기화되며(호이스팅), 함수 선언문은 통째로 저장된다. -
외부 환경 참조 결정: 함수가 선언된 위치의 렉시컬 환경을 참조하도록 설정한다. 이 덕분에 함수는 자신이 선언된 환경을 기억하고, 나중에 다른 곳에서 호출되더라도 해당 환경의 변수에 접근할 수 있다(클로저의 원리).
-
-
this 바인딩 결정: 함수가 어떻게 호출되었는지에 따라
this값을 결정하고 바인딩한다.
3.2. 실행 단계 (Execution Phase)
실행 컨텍스트 생성이 완료되면, 엔진은 함수 코드를 위에서 아래로 한 줄씩 실행한다.
-
코드 실행 및 값 할당: 변수에 값을 할당하고, 연산을 수행하며, 다른 함수를 호출하는 등의 작업을 진행한다.
-
다른 함수 호출: 만약 코드 실행 중 다른 함수를 호출하면, 새로운 함수 실행 컨텍스트가 생성되어 콜 스택의 맨 위에 쌓인다. 그리고 이 새로운 컨텍스트의 생성 및 실행 단계가 진행된다.
-
함수 종료: 함수의 실행이 끝나면 (return 문을 만나거나 코드의 끝에 도달하면), 해당 함수의 실행 컨텍스트는 콜 스택에서 **제거(pop)**된다. 그리고 제어권은 이전 실행 컨텍스트(스택의 바로 아래에 있던 컨텍스트)로 넘어간다.
4. 심화 탐구 실행 컨텍스트와 핵심 개념의 연결고리
이제 실행 컨텍스트의 렌즈를 통해 자바스크립트의 핵심 개념들을 다시 살펴보자.
4.1. 호이스팅 (Hoisting) 끌어올려지는 선언
호이스팅은 “선언이 코드의 최상단으로 끌어올려지는 것처럼 동작하는 현상”을 말한다. 이는 실행 컨텍스트의 생성 단계 때문에 발생한다.
-
var호이스팅: 생성 단계에서 변수 환경은var변수를undefined로 초기화한다. 따라서 실행 단계에서 변수 할당문 이전에 변수에 접근해도 에러가 발생하지 않고undefined가 반환된다. -
let/const와 TDZ: 생성 단계에서 렉시컬 환경은let과const변수를 기록하지만 초기화하지는 않는다. 이 상태에서 실행 단계 중 실제 선언문에 도달하기 전에 변수에 접근하려고 하면, 아직 초기화되지 않은 변수에 접근하는 것이므로 **참조 에러(ReferenceError)**가 발생한다. 이 구간을 **일시적 사각지대(TDZ)**라고 부른다. -
함수 선언문 호이스팅: 함수 선언문은 생성 단계에서 함수 객체 전체가 환경 레코드에 저장된다. 따라서 코드 어디에서든 함수를 호출할 수 있다. 반면 함수 표현식은
var,let,const의 규칙을 따르므로 호이스팅되지 않거나 TDZ의 영향을 받는다.
4.2. 스코프 체인 (Scope Chain) 변수를 찾는 여정
스코프 체인은 변수를 찾기 위한 탐색 경로다. 어떤 코드에서 변수에 접근할 때, 자바스크립트 엔진은 다음과 같은 순서로 변수를 찾는다.
-
현재 실행 컨텍스트의 렉시컬 환경에서 변수를 검색한다.
-
변수를 찾으면 검색을 중단하고 해당 변수를 사용한다.
-
변수를 찾지 못하면 **외부 환경 참조(Outer Environment Reference)**를 따라 상위 스코프의 렉시컬 환경으로 이동하여 검색을 계속한다.
-
이 과정을 전역 실행 컨텍스트에 도달할 때까지 반복한다.
-
전역 스코프에서도 변수를 찾지 못하면 **참조 에러(ReferenceError)**가 발생한다.
이처럼 각 실행 컨텍스트의 외부 환경 참조가 꼬리에 꼬리를 물고 연결된 형태가 바로 스코프 체인이다.
4.3. 클로저 (Closure) 기억하는 함수
클로저는 “함수와 그 함수가 선언된 렉시컬 환경의 조합”이다. 말이 어렵지만 실행 컨텍스트 관점에서 보면 간단하다.
-
내부 함수가 생성될 때, 해당 함수의 실행 컨텍스트는 자신의 외부 환경 참조로 외부 함수의 렉시컬 환경을 가리킨다.
-
외부 함수가 실행을 마치고 콜 스택에서 사라지더라도, 내부 함수는 여전히 외부 함수의 렉시컬 환경(변수, 스코프 등)을 참조하고 있다.
-
따라서 내부 함수는 나중에 어디서 호출되든, 자신이 태어난 환경(외부 함수의 변수들)을 기억하고 접근할 수 있다.
가비지 컬렉터는 이 참조 관계를 알고 있기 때문에, 외부 함수의 실행 컨텍스트가 종료되었음에도 불구하고 내부 함수가 참조하는 변수들은 메모리에서 해제하지 않는다. 이것이 클로저가 동작하는 원리다.
5. 실전 예제 코드로 이해하는 실행 컨텍스트
아래 코드를 통해 실행 컨텍스트의 흐름을 시각적으로 따라가 보자.
JavaScript
var globalVar = "전역 변수";
function outerFunc(outerParam) {
var outerVar = "외부 변수";
function innerFunc(innerParam) {
var innerVar = "내부 변수";
console.log(globalVar, outerParam, outerVar, innerParam, innerVar);
}
return innerFunc;
}
var myInnerFunc = outerFunc("외부 파라미터");
myInnerFunc("내부 파라미터");
실행 흐름:
-
전역 실행 컨텍스트 생성:
-
globalVar와outerFunc,myInnerFunc를 환경 레코드에 등록한다.globalVar와myInnerFunc는undefined로 초기화된다.outerFunc는 함수 객체로 저장된다. -
콜 스택:
[ 전역 실행 컨텍스트 ]
-
-
전역 코드 실행:
-
globalVar에 “전역 변수”가 할당된다. -
outerFunc("외부 파라미터")가 호출된다.
-
-
outerFunc실행 컨텍스트 생성:-
생성 단계:
-
렉시컬 환경:
outerParam(“외부 파라미터”),outerVar(undefined),innerFunc(함수 객체)를 기록. -
외부 환경 참조: 전역 렉시컬 환경을 가리킴.
-
this: 전역 객체 (window)
-
-
콜 스택:
[ 전역 실행 컨텍스트, outerFunc 실행 컨텍스트 ] -
실행 단계:
-
outerVar에 “외부 변수”가 할당된다. -
innerFunc함수 객체를 반환한다.
-
-
outerFunc실행이 종료되고 콜 스택에서 제거(pop)된다. -
콜 스택:
[ 전역 실행 컨텍스트 ] -
반환된
innerFunc가 전역 변수myInnerFunc에 할당된다. 이때myInnerFunc는outerFunc의 렉시컬 환경(스코프)을 기억하는 클로저가 된다.
-
-
myInnerFunc("내부 파라미터")호출:-
innerFunc실행 컨텍스트가 생성된다. -
생성 단계:
-
렉시컬 환경:
innerParam(“내부 파라미터”),innerVar(undefined)를 기록. -
외부 환경 참조:
outerFunc의 렉시컬 환경을 가리킴 (이것이 클로저의 핵심!). -
this: 전역 객체 (window)
-
-
콜 스택:
[ 전역 실행 컨텍스트, innerFunc 실행 컨텍스트 ] -
실행 단계:
-
innerVar에 “내부 변수”가 할당된다. -
console.log()실행:-
innerVar,innerParam: 현재 스코프에서 찾음. -
outerVar,outerParam: 외부 환경 참조를 따라outerFunc의 렉시컬 환경에서 찾음. -
globalVar: 다시 외부 환경 참조를 따라 전역 렉시컬 환경에서 찾음.
-
-
콘솔에 “전역 변수 외부 파라미터 외부 변수 내부 파라미터 내부 변수”가 출력된다.
-
-
innerFunc실행이 종료되고 콜 스택에서 제거(pop)된다. -
콜 스택:
[ 전역 실행 컨텍스트 ]
-
6. 마치며 실행 컨텍스트를 정복한 당신에게
지금까지 우리는 자바스크립트 엔진의 가장 깊숙한 곳을 여행했다. 실행 컨텍스트는 눈에 보이지 않지만, 우리가 작성하는 모든 코드 라인의背后에서 묵묵히 자신의 역할을 수행하고 있다.
이제 당신은 더 이상 undefined를 만나도 당황하지 않을 것이다. 왜 let과 const가 TDZ를 가지는지, 왜 함수를 선언하기도 전에 호출할 수 있는지, 그리고 클로저가 어떻게 마법처럼 상위 스코프의 변수를 기억하는지 설명할 수 있게 되었다.
실행 컨텍스트에 대한 이해는 단순히 지식을 넘어선다. 이것은 코드를 예측하고, 버그를 디버깅하며, 더 효율적이고 안정적인 코드를 작성하는 강력한 무기가 된다. 자바스크립트라는 언어와 더 깊은 대화를 나누고 싶다면, 실행 컨텍스트라는 엔진의 언어를 반드시 이해해야 한다. 이 핸드북이 그 여정의 훌륭한 첫걸음이 되었기를 바란다.