🍀Gyuri's Devlog
FEJS

V8 엔진에서 실행 컨텍스트는 어떻게 만들어질까?

실제 엔진에서는 어떻게 동작할까?

앞서 언급한 ECMAScript specJavaScript의 동작 규칙을 정의하는 공식 문서입니다. 하지만 spec은 개념적인 동작만 정의합니다. 예를 들어 다음과 같은 것들입니다.

  • Execution Context (실행 컨텍스트)
  • Lexical Environment (렉시컬 환경)
  • Environment Record (환경 레코드)

하지만 이 구조들이 실제로 어떤 객체로 만들어지고 메모리에 어떻게 저장되는지는 정의하지 않습니다. 이 부분은 JavaScript 엔진의 구현 영역입니다.

JavaScript 엔진(V8, SpiderMonkey 등)은 ECMAScript spec을 기준으로 자신만의 방식으로 내부 구조를 구현합니다. 그렇다면 실제 엔진에서는 실행 컨텍스트가 어떻게 만들어질까요?

실제 과정

function foo() {
  const a = 1;
  var b = 1;

  if (true) {
    let c = 2;
  }
}

foo(); // 함수 호출

위 코드를 실행해서 함수를 호출하면, 정확하게는 전역 실행 컨텍스트가 먼저 생성되지만, 핵심만 간단하게 설명하기 위해 이 내용은 생략하겠습니다.

1️⃣ foo() 호출

foo() 함수가 호출되어 실행되면, 먼저 foo에 대한 실행 컨텍스트가 생성됩니다.

2️⃣ Function Environment 생성

foo의 실행 컨텍스트에 담길 환경 변수를 객체로 생성합니다. 그리고 메모리에 올라갑니다.

Function Environment (실제 메모리에 올라가는 환경 객체)
 ├─ EnvironmentRecord
 └─ outer → Global Environment (전역 환경)

EnvironmentRecord비어있는 상태입니다.

Function Environment (실제 메모리에 올라가는 환경 객체)
 └─ EnvironmentRecord
		 (empty)

3️⃣ Execution Context 연결

실행 컨텍스트의 내부 필드는 실질적으로 아래와 같이 구성됩니다. 즉, LexicalEnvironment === VariableEnvironment 이 됩니다.

Execution Context (실행 컨텍스트)
 ├─ LexicalEnvironment ──┐
 └─ VariableEnvironment ─┘
            │ (둘다 같은 객체를 가리키게 된다)

      Function Environment

4️⃣ 생성 단계 (Creation Phase)

코드를 실행하기 전에 변수, 함수, 스코프 환경을 먼저 준비합니다. 이 단계를 생성 단계라고 합니다. 함수 실행 컨텍스트 초기화 과정에서 엔진은 다음 순서로 처리합니다.

1. parameter bindings (파라미터 바인딩)
2. function declarations (함수 선언)
3. var declarations (var 선언
4. lexical declarations (let / const)

위와 같이 parameter / function / var 먼저 등록합니다. (VariableEnvironment이 원래 처리하던거)

function foo() {
  const a = 1;
  var b = 2;

  if (true) {
    let c = 3;
    var d = 4;
  }
}

foo(); // 함수 호출

여기서 중요한 점은 varblock을 무시하고 함수 스코프에 생성됩니다. 위 코드에서 var로 선언된 변수 bd가 발견되었으니 아래와 같이 환경 객체에 초기화가 이뤄집니다.

Function Environment (실제 메모리에 올라가는 환경 객체)
└─ EnvironmentRecord
   ├─ b → undefined
	 └─ d → undefined

그다음 lexical binding을 생성합니다. (let, const)

Function Environment (실제 메모리에 올라가는 환경 객체)
	└─EnvironmentRecord
	   ├─ b → undefined
		 ├─ d → undefined
		 └─ a → <uninitialized>

결과적으로 생성 단계를 거치고 나면 위와 같은 환경 객체가 만들어지고, varconst는 같은 EnvironmentRecord에 존재하게 됩니다. 그래서 아래와 같은 상태가 됩니다.

Execution Context
 ├─ LexicalEnvironment ──┐
 └─ VariableEnvironment ─┘


      Function Environment (환경 객체)


      EnvironmentRecord
        ├─ b → undefined
        ├─ d → undefined
        └─ a → <uninitialized>

즉 여전히 LexicalEnvironment === VariableEnvironment 상태입니다.

5️⃣ 함수 실행 (런타임)

function foo() {
  const a = 1;
  var b = 2;

  if (true) {
    let c = 3;
    var d = 4;
  }
}

foo(); // 함수 호출

런타임이 되면 하나씩 실행됩니다. 그래서 Function Environment (환경 객체)가 아래처럼 갱신됩니다.

Execution Context
 ├─ LexicalEnvironment ──┐
 └─ VariableEnvironment ─┘


      Function Environment (환경 객체)


      EnvironmentRecord
        ├─ b → 2
        ├─ d → undefined
        └─ a → 1

6️⃣ if 문의 block 진입

if (true) {
  let c = 3;
  var d = 4;
}

이 시점에서 Block Environment가 새로 생성됩니다. 이것도 실제 메모리에 올라가는 객체를 이름 지은 것입니다.

Block Environment
 ├─ EnvironmentRecord
 │    └─ c → <uninitialized>
 └─ outer → Function Environment

이때 LexicalEnvironment 렉시컬 환경와 VariableEnvironment 변수 환경이 가리키는게 달라집니다. LexicalEnvironment ≠ VariableEnvironment
Execution Context
 ├─ LexicalEnvironment → Block Environment
 └─ VariableEnvironment → Function Environment

7️⃣  if 문의 block 실행 실행

if (true) {
  let c = 3; // (1)
  var d = 4; // (2)
}

이 부분이 실행 완료되면, 아래와 같이 갱신된다.

Execution Context
 ├─ LexicalEnvironment ───────────────┐
 │                                    ▼
 │                            Block Environment (메모리에 올라간 블록 환경 객체)
 │                              ├─ EnvironmentRecord
 │                              │     └─ c → 3 // (1)
 │                              └─ outer (상위 스코프) 
 │                                    │
 │                                    ▼
 └─ VariableEnvironment ───── Function Environment (메모리에 올라간 함수 환경 객체)
                                ├─ a → 1
                                ├─ b → 2
                                └─ d → 4 // (2)

8️⃣ if block문 종료

if 블록 문이 종료되면, Block Environment는 제거 된다. 최종적으로는 아래 구조로 마무리된다.

Execution Context
 ├─ LexicalEnvironment
 └─ VariableEnvironment


   Function Environment
				  └─ EnvironmentRecord
							 ├─ b → 1
							 ├─ d → 3
							 └─ a → 1