실행 컨텍스트(Execution Context)는 뭘까요?
FE 기술 면접에서 많이 물어보는 질문들 중 대다수가 실행 컨텍스트와 관련이 있으며 자바스크립트 DeepDive 책에도 설명되어 있습니다.
그만큼 실행 컨텍스트는 자바스크립트의 동작 원리를 담고 있는 핵심 개념이라고 할 수 있습니다. 그리고 실행 컨텍스트의 동작 원리를 제대로 이해하면 식별자 바인딩, 호이스팅, 클로저, 이벤트의 동작 원리 등을 이해하는데 수월합니다.
소스코드(ECMAScript code), 즉 실행이 가능한 코드는 크게 4가지 타입으로 구분되며 실행 컨텍스트를 생성합니다. 그리고 소스코드 타입에 따라 생성된 실행 컨텍스트는 생성 과정과 관리 내용이 다르다는 차이가 있습니다.
1️⃣ 전역 코드 (global code)
전역에 존재하는 소스코드를 의미하며 전역 코드가 평가되면 전역 실행 컨텍스트가 생성됩니다. 이때 전역에 정의된 함수, 클래스 등의 내부 코드는 포함되지 않습니다.
2️⃣ 함수 코드 (function code)
함수 내부에 존재하는 소스코드를 의미하며 함수 코드가 평가되면 함수 실행 컨텍스트가 생성됩니다. 이때 함수 내부에 중첩된 함수, 클래스 등의 내부 코드는 포함되지 않습니다.
3️⃣ eval 코드 (eval code)
eval 함수에 인수로 전달돼 실행되는 소스 코드를 의미하며 eval 코드가 평가되면 eval 실행 컨텍스트가 생성됩니다.
4️⃣ 모듈 코드 (module code)
모듈 내부에 존재하는 소스코드로 모듈 코드가 평가되면 모듈 실행 컨텍스트가 생성됩니다. 이때 모듈 내부의 함수, 클래스 등의 내부 코드는 포함되지 않습니다.
자바스크립트 엔진은 소스코드를 평가와 실행, 2단계로 나누어 처리합니다.
1️⃣ 평가 과정 (혹은 생성 단계, creation phase)
① 실행 컨텍스트를 생성
② 변수, 함수 등의 선언문만 먼저 실행
③ 생성된 변수/함수 식별자를 키(key)로 스코프에 등록
2️⃣ 실행 과정 (실행 단계, execution phase)
① 선언문을 제외한 소스코드가 순차적으로 실행 (런타임 시작)
② 실행에 필요한 정보를 스코프에서 검색해 취득
③ 소스코드에 의해 변경된 실행 결과는 다시 스코프에 등록
코드가 실행되려면 스코프, 식별자, 코드의 실행 순서 등 여러 부분에서 관리가 필요하며 이러한 관리를 해주는 것이 실행 컨텍스트 입니다.
[자세한 설명/조건]
① 선언에 의해 생성된 모든 식별자는 스코프를 구분해 등록하고 상태 변화를 지속적으로 관리할 수 있어야 한다.
② 스코프는 중첩 관계에 의해 스코프 체인을 형성해야 한다.
스코프 체인을 통해 상위 스코프로 이동하며 식별자를 검색할 수 있어야 한다.
③ 현재 실행 중인 코드의 실행 순서를 변경할 수 있어야 하며 다시 되돌아갈 수도 있어야 한다.
실행 컨텍스트는 식별자를 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 매커니즘으로 모든 코드는 실행 컨텍스트를 통해 실행 및 관리됩니다.
식별자와 스코프는 실행 컨텍스트의 렉시컬 환경으로 관리하고 코드의 실행 순서는 실행 컨텍스트 스택으로 관리합니다.
실행 컨텍스트의 스택은 코드의 실행 순서를 관리합니다.
소스코드가 평가되면 실행 컨텍스트가 생성되고 실행 컨텍스트의 최상위에 쌓이게 됩니다. 그리고 실행 컨텍스트 스택의 최상위에 존재하는 실행 컨텍스트는 언제나 현재 실행 중인 코드의 실행 컨텍스트입니다. 그래서 실행 컨텍스트 스택의 최상위 실행 컨텍스트를 실행 중인 실행 컨텍스트(running execution context)라고 합니다.
예를 들어 아래와 같은 소스코드가 실행 컨텍스트 스택에 쌓일 때는 아래 이미지와 같을 것입니다.
function outer(){
// ...codes
function inner(){
// ...codes
}
inner();
}
outer();
렉시컬 환경(lexical environment)은 식별자와 식별자에 바인딩된 값 그리고 상위 스코프에 대한 참조를 기록하는 자료구조입니다.
렉시컬 환경은 키와 값을 갖는 객체 형태의 스코프를 생성해 식별자를 키(key)로 등록하고 식별자에 바인딩된 값을 관리합니다. 즉, 렉시컬 환경은 스코프를 구분해 식별자를 등록하고 관리하는 저장소 역할을 하는 렉시컬 스코프의 실체라고 할 수 있습니다.
실행 컨텍스트는 Lexical Environment 컴포넌트와 Variable Environment 컴포넌트로 구성됩니다.
생성 초기의 실행 컨텍스트와 렉시컬 환경을 그림으로 표현하면 아래와 같습니다.
생성 초기에 두 컴포넌트는 하나의 동일한 렉시컬 환경을 참조하게 됩니다.
렉시컬 환경는 환경 레코드와 외부 렉시컬 환경에 대한 참조, 두 개의 컴포넌트로 구성되어 있습니다.
1️⃣ 환경 레코드 (environment record)
2️⃣ 외부 렉시컬 환경에 대한 참조 (outerLexical environment reference)
다시 한 번 정리하기 위해 스택에서 설명했던 예시를 그대로 사용해 그림으로 나타내면 아래와 같습니다.
Variable Environment에서는 현재 컨텍스트 내의 식별자들에 대한 정보와 외부 환경 정보를 관리합니다.
Variable Environment를 선언 시점의 Lexical Environment의 스냅샷과 같다고 생각하면 편합니다. 스냅샷이기 때문에 변경된 사항이 반영되지는 않습니다.
Lexical Environment의 초기는 Variable Environment과 동일하지만 변경 사항이 실시간으로 반영되기 때문에 내용이 달라지게 됩니다.
실행 컨텍스트를 생성할 때 Variable Environment에 정보를 먼저 담은 다음, 이를 그대로 복사해 Lexical Environment를 만듭니다. 그런 이후에는 Lexical Environment를 주로 활용하게 됩니다.
소스코드가 로드(load)되면 자바스크립트 엔진은 전역 코드를 평가합니다.
전역 코드 평가는 다음과 같은 순서로 진행됩니다.
가장 먼저 비어 있는 전역 실행 컨텍스트를 생성해 실행 컨텍스트 스택에 푸시(push)합니다.
동시에 전역 실행 컨텍스트는 실행 중인 실행 컨텍스트가 됩니다.
다음으로 전역 렉시컬 환경을 생성하고 전역 실행 컨텍스트에 바인딩합니다.
렉시컬 환경은 위에서도 설명했듯 환경 레코드와 외부 렉시컬 환경에 대한 참조, 두 개의 컴포넌트로 구성되어 있습니다.
전역 렉시컬 환경을 구성하는 컴포넌트인 전역 환경 레코드는 아래 세 가지를 제공합니다.
1. 전역 변수를 관리하는 전역 스코프
2. 전역 객체의 built-in 전역 프로터티와 built-in 전역 함수
3. 표준 built-in 객체
전역 환경 레코드는 객체 환경 레코드와 선언전 환경 레코드로 구성되어 있습니다.
두 개의 컴포넌트는 서로 협력해 전역 스코프와 전역 객체를 관리합니다.
1️⃣ 객체 환경 레코드
1. var 키워드로 선언한 전역 변수
2. 함수 선언문으로 정의한 전역 함수
3. built-in 전역 프로퍼티
4. built-in 전역 함수
5. 표준 built-in 객체
2️⃣ 선언전 환경 레코드
1. let, const 키워드로 선언한 전역 변수
객체 환경 레코드는 BindingObject라는 객체와 연결되는데 이때 BindingObject는 전역 객체 생성에서 생성된 전역 객체입니다.
전역 코드 평가 과정에서 전역 변수(var 키워드로 선언)와 전역 함수는 BindingObject를 통해 전역 객체의 프로퍼티와 메서드가 됩니다.
이것이 전역 변수(var 키워드로 선언)와 전역 함수가 전역 객체를 가리키는 식별자(window) 없이 전역 객체의 프로퍼티를 참조할 수 있는 매커니즘입니다.
example ) window.alert을 alert으로 참조
객체 환경 레코드 외 렉시컬 환경을 구성하는 컴포넌트 중 하나인 선언적 환경 레코드도 생성됩니다. let, const 키워드로 선언한 전역 변수가 여기에 등록되고 관리됩니다.
선언전 환경 레코드는 전역 객체의 프로퍼티가 되지 않기 때문에 전역 객체 프로퍼티로서 참조가 불가능합니다.
또한 const 키워드로 선언한 변수는 선언 단계와 초기화 단계가 분리되어 진행되므로 일시적 사각지대(TDZ : Temporal Dead Zone)에 빠지게 됩니다.
전역 환경 레코드의 [[GlobalThisValue]] 내부 슬롯에 this가 바인딩됩니다.
일반적으로 전역 코드에서 this는 전역 객체를 가리키기 때문에 전역 객체가 바인딩되게 됩니다.
참고로 위 그림에서도 알 수 있지만, 객체 환경 레코드와 선언적 환경 레코드에는 this 바인딩이 없습니다.
외부 렉시컬 환경에 대한 참조는 현재 평가 중인 소소코드를 포함하는 외부 소스코드의 상위 스코프를 가리키기 때문에 이를 통해 스코프 체인을 구현할 수 있게 됩니다.
현재 평가 중인 소스코드는 전역 코드이며 전역 코드를 포함하는 소스코드는 없기 때문에 여기에 null이 할당되게 됩니다. 이는 전역 렉시컬 환경이 스코프 체인의 종점에 존재함을 의미합니다.
실행 컨텍스트란?
실행 컨텍스트는 실행할 코드에 제공할 환경 정보를 모아놓은 객체로 해당 객체에는 변수 객체, 스코프 체인, this 정보가 담겨있습니다.
이 객체들을 콜 스택(실행 컨텍스트 스택)에 담아 순차적으로 실행시킵니다.
자동으로 전역 컨텍스트가 생성된 후 함수 호출시마다 함수 컨텍스트가 생성되고, 컨텍스트 생성이 완료된 후에 함수가 실행됩니다.
함수 실행 중에 사용 되는 변수들을 변수 객체 안에서 값을 찾고 값이 존재하지 않는다면 Lexical 환경의 outerEnvironmentReference(외부 렉시컬 환경에 대한 참조)를 통해 Scope 체인을 따라 올라가면서 탐색합니다.
함수 실행이 마무리가 되면 해당 컨텍스트는 사라지고, 페이지가 종료되면 전역 컨텍스트도 사라집니다.
this 란?
실행 컨텍스트의 this 바인딩에는 this로 지정된 객체가 저장됩니다.
실행 컨텍스트 활성화 당시에 this가 지정되지 않은 경우엔 this에 전역 객체가 저장됩니다.
그 밖에는 함수를 호출하는 방법에 따라 저장되는 대상이 달라집니다.
References.
📚 mostly based on. modern javascript DeepDive
👩🏻💻 posts.
🎥 videos.
🖼️ images.
recreated by @55555-Jyeon