
실행 컨텍스트는 자바스크립트의 코드가 어떤 방식으로 동작하는지 이해하는데 필요한 개념으로, 싱글 스레드로 동작하는 자바스크립트에서는 함수 호출이나 전역 코드 실행 시마다 각각의 실행 컨텍스트가 생성됩니다.
이러한 실행 컨텍스트는 코드가 실행되는 환경을 설정하고 관리하며, 스코프와 식별자, 그리고 this가 어떤 방식으로 동작하는지를 결정하는 역할을 합니다.
실행컨텍스트의 생성 과정과 관리 내용은 소스코드(실행가능한 코드)의 타입에 따라 달라집니다.
자바스크립트는 코드가 실행될 때, 소스코드의 타입에 따라 해당 코드에 맞는 실행 컨텍스트를 생성하여 관리합니다. 현재 ECMAScript 사양은 소스코드 타입을 다음과 같이 네 가지로 구분하고 있습니다.
전역 코드는 전역 변수를 관리하고, 최상위 스코프인 전역 스코프를 생성해야 합니다. var 키워드로 선언된 전역 변수와 함수 선언문으로 정의된 전역 함수는 전역 객체의 프로퍼티 및 메서드로 바인딩되며, 전역 객체와 연결됩니다. 이를 위해 전역 코드가 평가될 때 전역 실행 컨텍스트가 생성됩니다.
함수 코드는 지역 스코프를 생성하고, 함수 내에서 사용되는 지역 변수, 매개변수, 그리고 arguments 객체를 관리합니다. 생성된 지역 스코프는 스코프 체인의 일부로서 전역 스코프와 연결됩니다. 함수 코드가 평가될 때 함수 실행 컨텍스트가 생성됩니다.
자바스크립트의 빌트인 함수인 eval에 인수로 전달된 코드는 strict mode(엄격모드)에서 자신만의 독립적인 스코프를 생성합니다. 이를 위해 eval 코드가 평가될 때 eval 실행 컨텍스트가 생성됩니다. (엄격 모드가 아닐 경우에는 기존 스코프를 사용합니다.)
모듈 코드는 모듈별로 독립적인 모듈 스코프를 생성하며, 전역 스코프와는 별개로 동작합니다. 각 모듈은 자체적인 스코프를 가지므로, 모듈 코드가 평가될 때 모듈 실행 컨텍스트가 생성됩니다.
실행컨텍스트가 생성되고 관리를 진행하기 위해서는 소스코드 자체가 평가되고 실행되어야 합니다.
자바스크립트의 모든 소스코드는 실행하기 앞서 평가의 과정을 거치며 코드를 실행하기 위한 준비를 합니다.
소스코드 평가 과정에서는 실행 컨텍스트를 생성하고, 변수 선언이나 함수 선언문 등과 같은 선언문들을 먼저 실행합니다. 선언된 변수나 함수는 실행 컨텍스트에서 관리하는 스코프, 즉 렉시컬 환경에 등록되며, 변수는 undefined로 초기화됩니다.
평가가 끝난 후 소스코드 실행 과정에서는 자바스크립트는 실제로 코드를 실행하면서 변수에 값을 할당하거나 함수 호출을 처리하는 작업을 진행합니다.
var beer
beer = 'GUINESS'
그럼 위 코드는 어떻게 평가와 실행으로 나누어질까요?
var beer;
beer = 'GUINESS'
즉, 평가 단계에서는 beer 변수를 스코프에 등록하고 초기화하며, 실행 단계에서 그 변수에 값을 할당하는 것입니다.
이 개념을 실제 맥주를 사는 비유로 설명하면, 평가 단계는 "오늘 맥주를 마셔야겠다고 생각하는 것"과 같고, 실행 단계는 실제로 마트에 가서 맥주를 사는 과정에 비유할 수 있습니다.
맥주를 마셔야겠다고 생각하는 것은 맥주라는 아이디어가 머릿속에 있는 것이지만, 실제로 맥주가 손에 들어온 건 아닙니다. 그러다 마트에서 맥주를 실제로 사는 순간, 변수에 값을 할당하는 것처럼 맥주를 구체적으로 소유하게 되는 것입니다.
// 전역변수 x, y 선언
const x = 1; // 1
const y = 2; // 2
// test 함수 정의
function test(a) {
// 지역변수 x, y 선언
const x = 10;
const y = 20;
// 메서드 호출
console.log(a+x+y); //130
}
// test 함수 호출
test(100);
// 메서드 호출
console.log(x+y); //3
이 코드는 130이 먼저 출력되고, 이후 3이 출력됩니다. 위 코드처럼 전역코드와 함수코드가 함께 있을때 실행 순서를 결정하는 것이 바로 실행 컨텍스트입니다. 이 코드를 실행하는 과정에서 실행 컨텍스트가 어떤 역할을 하는지, 평가와 실행이 어떻게 이루어지는지 살펴보겠습니다.
코드가 실행되기 전에, 전역 코드 평가 과정이 먼저 수행됩니다. 이때 선언문만 실행되며, 변수와 함수가 실행 컨텍스트의 전역 스코프에 등록됩니다.
위 코드에서는 const x, const y, 그리고 function test가 평가되며, 전역 스코프에 등록됩니다.
이때 var 키워드로 선언된 변수는 전역 객체와 연결되지만, 여기서는 const를 사용했기 때문에 전역 객체와는 별개로 관리됩니다.
전역 코드 평가가 완료되면, 런타임이 시작되어 전역 코드가 순차적으로 실행됩니다. 이때 전역 변수 x와 y에 값이 할당되고, test 함수가 호출됩니다.
함수가 호출되면 순차적으로 실행되던 전역 코드의 실행이 일시 중단되고, 코드 실행 순서를 변경하여 함수 내부로 진입하게 됩니다.
함수 내부로 진입하게 되면,먼저 함수 내부 코드를 실행하기 전에 함수 코드 평가 과정이 이루어집니다. 이 평가 과정에서 매개변수와 함수 내부에서 선언된 지역 변수들이 실행 컨텍스트의 지역 스코프에 등록됩니다.
위 코드에서는 매개변수 a가 값 100을 받고, 지역 변수 x는 10, y는 20이 할당되며 지역 스코프에 등록되게 됩니다. 또한 함수 내부에서 사용할 수 있는 arguments 객체도 생성되어 지역 스코프에 등록됩니다.
이때 스코프 체인이 형성되어 함수 내부에서 식별자를 검색할 때 현재 지역 스코프뿐만 아니라 상위 스코프(전역 스코프) 도 참조할 수 있게 됩니다.
함수 코드 평가가 완료되면 이제 함수의 코드 실행이 시작됩니다.
함수 내부에서 console.log(a + x + y)를 호출하게 되며, 이때 a는 매개변수로 전달된 100, 지역 변수 x는 10, y는 20으로 a + x + y는 130이 됩니다.
여기서 중요한 점은 console 객체는 전역 객체의 프로퍼티로 존재하며, 스코프 체인을 통해 함수 내부에서 전역 스코프를 참조해 console을 찾습니다. 이처럼 실행 컨텍스트는 코드가 실행될 때, 스코프 체인을 통해 필요한 식별자를 검색하고 값을 참조하는 역할을 합니다.
이후 함수 코드가 모두 실행되면 함수 호출이 종료되고, 현재의 실행 컨텍스트는 제거됩니다. 실행 순서는 함수 호출 직전으로 돌아가서 전역 코드의 실행이 계속됩니다.
함수 호출이 종료된 후 전역 코드로 다시 돌아와, 마지막으로 console.log(x + y)가 실행됩니다.
이때 전역 스코프에 등록된 x = 1과 y = 2를 참조하여 3이 출력됩니다.
위 과정을 통해 알 수 있는 실행 컨텍스트의 역할은, 각 코드 블록(전역 코드, 함수 코드 등)이 실행되기 전 미리 준비를 마치는 데 있습니다. 이때 평가 과정을 거쳐 해당 블록에서 사용되는 변수나 함수 등의 식별자들을 스코프에 등록하고, 코드 실행 도중에는 스코프 체인을 통해 상위 스코프에 있는 식별자도 검색할 수 있도록 연결합니다.
또한 실행 중인 코드의 흐름을 제어하고, 함수 호출과 같은 새로운 블록이 실행되면 실행 흐름을 잠시 멈추고 새로운 실행 컨텍스트를 생성합니다. 마지막으로, 코드 블록이 종료되면 다시 이전 코드로 돌아갈 수 있도록 실행 순서를 기억하고 관리합니다.
실행 컨텍스트는 식별자(변수, 함수 등의 이름)을 등록하고 관리하는 스코프와 코드의 코드의 실행 순서를 관리하는 메커니즘입니다.
코드의 실행 순서는 어떻게 관리될까요? 이를 관리하는 것이 바로 실행 컨텍스트 스택입니다. 스택은 후입 선출(Last In First Out, LIFO) 구조를 가진 자료구조로, 마지막에 추가된 컨텍스트가 먼저 처리되는 방식으로 동작합니다.
스택의 개념을 엘리베이터에 비유할 수 있습니다. 엘리베이터의 문이 하나뿐이라면, 먼저 탄 사람들이 안쪽에 들어가고, 나중에 탄 사람들이 바깥에 위치하게 됩니다. 엘리베이터가 도착하면 가장 바깥에 있는 사람들이 먼저 내리는 구조, 바로 이것이 스택의 후입 선출의 원리입니다.
실행 컨텍스트 스택에서는 코드가 실행될 때마다 실행 컨텍스트가 생성되고, 해당 컨텍스트가 스택에 추가(Push) 됩니다. 코드 블록의 실행이 끝나면 해당 컨텍스트는 스택에서 삭제(Pop) 되어, 그 이전 단계로 돌아가게 됩니다.
이번에는 아래 코드를 실행 컨텍스트 스택에 대입해서 추가(Push), 삭제(pop)과정으로 살펴보겠습니다.
const x = 1; // 전역 실행 컨텍스트
function foo(a) {
const y = 2;
function bar() {
const z = 3;
console.log(x+y+z);
}
bar();
}
foo() // 6
가장 먼저 전역 코드가 평가되어 전역 실행 컨텍스트가 생성됩니다. 이때 전역 변수 x와 함수 foo가 전역 실행 컨텍스트에 등록되며, 스택에 Push 됩니다.
전역 코드가 실행되기 시작하면서 변수 x에 값이 할당되고, 함수 foo가 호출됩니다.
foo 함수가 호출되면, 제어권이 foo 함수 내부로 이동합니다. 이때 foo 함수 실행 컨텍스트가 생성되고 스택에 Push 됩니다.
foo 함수의 실행 컨텍스트에는 지역 변수 y와 중첩 함수 bar가 등록됩니다.이후 foo 함수의 코드가 실행되면서 y에 값이 할당되고, bar 함수가 호출됩니다.
bar 함수가 호출되면, bar 함수 실행 컨텍스트가 생성되어 스택에 Push 됩니다.console.log(x + y + z) 메서드가 실행되고 x, y, z의 값을 더해 6이 출력됩니다.
bar 함수가 종료되면 코드의 제어권은 다시 foo함수로 이동하고, bar 함수가 종료되면서 스택에서 Pop 되어 사라집니다. foo함수도 더 이상 실행할 코드가 없으므로 종료됩니다.
foo 함수가 종료되면 제어권은 다시 전역 코드로 이동하고, foo함수 실행 컨텍스트를 실행컨텍스트 스택에서 pop하여 제거합니다. 또한 더이상 실행할 전역 코드가 남아있지 않으므로 전역 실행 컨텍스트도 pop되어 실행 컨텍스트 스택에는 아무것도 남아있지 않게 됩니다.

이처럼 실행컨텍스트 스택은 코드가장 밖에 있는 전역 코드부터 실행하면서 어떤 코드를 먼저 실행할지 순서를 드의 실행 순서를 관리합니다.
실행 컨텍스트 스택은 코드의 실행 순서를 효율적으로 관리하는데, 가장 최근에 실행된 함수나 코드 블록의 실행이 끝나면 이전 단계로 돌아가도록 실행 컨텍스트를 Push 및 Pop하는 방식으로 처리합니다.
최상위에 위치한 실행 컨텍스트는 언제나 현재 실행 중인 코드 블록의 컨텍스트입니다. 이를 실행 중인 실행 컨텍스트라고 부르며, 스택 구조 덕분에 각 코드 블록이 끝날 때마다 자연스럽게 이전 단계로 돌아가며 실행 순서를 관리할 수 있습니다. 이처럼 실행 컨텍스트 스택은 코드 실행 흐름을 효율적으로 제어하는 중요한 메커니즘입니다.
실행 컨텍스트는 내용이 너무 길어 일단 개념과 예시를 통해 이해해보는 내용에 대해서만 정리했습니다..!
(렉시컬 환경도 같은 장에 나오는데.. 다음 기회에..)
앞으로 공부하면서 추가로 알게되는 내용은 다시 정리를 해야겠습니다 🥲
출처: 모던 자바스크립트 Deep Dive 23장 실행 컨텍스트 (359p ~ 387p)