likeornament 님의 블로그
VariableEnvironment는 왜 ‘쓰이지 않도록’ 설계되었을까 본문
실행 컨텍스트에는 코드 실행에 필요한 환경 정보들이 담긴다.
이 환경 정보에는 크게 VariableEnvironment와 LexicalEnvironment로 나뉜다.
- VariableEnvironment
- 현재 컨텍스트 내의 식별자들에 대한 정보와 외부 환경 정보를 담는다.
- 실행 컨텍스트가 생성되는 시점의 LexicalEnvironment를 스냅샷으로 고정한 구조다.
- 이후 실행 과정에서의 변경 사항은 반영되지 않는다. - LexicalEnvironment
- 처음에는 VariableEnvironment와 동일하다.
- 코드 실행 중 발생하는 변경 사항이 실시간으로 반영된다.
두 환경은 내부적으로 아래의 동일한 구조를 가진다.
- environmentRecord
- 현재 컨텍스트와 관련된 식별자 정보를 저장한다.
- 컨텍스트 내부 코드를 처음부터 끝까지 훑으며 선언을 수집한다. - outerEnvironmentReference
- 식별자의 유효범위를 안쪽에서 바깥쪽으로 탐색할 수 있도록 하는 연결 정보다.
이 구조 덕분에 environmentRecord에서는 호이스팅이,
outerEnvironmentReference에서는 스코프와 스코프 체인이 설명된다.
실행 컨텍스트가 생성될 때,
엔진은 먼저 VariableEnvironment에 정보를 수집한 뒤 이를 복사해 LexicalEnvironment를 만든다.
하지만 그 이후의 상황을 보면 의문이 생긴다.
- 모든 식별자 탐색은 LexicalEnvironment 기준으로 이루어 진다.
- 실행 도중 변경되는 값 역시 LexicalEnvironment에만 반영된다.
- VariableEnvironment는 실행 흐름에서 거의 다시 등장하지 않는다.
VariableEnvironment는 실제 실행에 사용되지 않는데 굳이 왜 분리해 두었을까?
이 글에서는 VariableEnvironment가 없는 단일 환경이라고 가정하고,
그 가정이 어디서 깨지는지를 확인해 존재 이유를 확인해 보려 한다.
들어가기에 앞서,
VariableEnvironment는 코드로 직접 관찰할 수 있는 대상이 아니다.
하지만 다음과 같은 현상들은 코드로 확인할 수 있다.
- 선언과 실행이 분리되어 있다는 사실
- 실행 전에 이미 식별자가 존재한다는 점
- 변수 선언과 함수 선언의 초기 상태가 다르다는 점
이 현상들이 VariableEnvironment 없이도 설명 가능한지를 살펴보는 것이 핵심이다.
1. undefined의 두 가지 상태
var a;
console.log(a); // undefined
위 코드에서 출력되는 undefined만을 개발자가 봤을 때 두 가지 의미로 해석할 수 있다.
- 선언만 이루어지고 초기값으로 undefined가 설정된 상태
- 실행 중 명시적으로 undefined가 할당된 상태
단일 환경 모델에서는 이 두 상황이 모두 그저 a: undefined로 표현된다.
즉, "선언 단계에서 초기화된 undefined"와 "실행 중 명시적으로 할당된 undefined"를 구조적으로 구분하기 어렵다.
그러나 VariableEnvironment가 존재하면 이야기가 달라진다.
- 식별자의 존재 여부는 실행 컨텍스트 생성 시점에서 이미 확정된다.
- 이후 값의 변경은 LexicalEnvironment에서만 이루어진다.
이렇게 선언 수집 단계와 실행 단계에서의 변경을 서로 다른 역할로 분리해 설명할 수 있다.
2. 선언 시점과 실행 시점의 경계
function test() {
console.log(a);
var a = 10;
console.log(a);
}
test();
// 실행 결과: undefined, 10
이 코드를 실행하기 전부터 a 는 이미 존재한다.
하지만 값은 실행 흐름이 해당 지점에 도달했을 때 비로소 할당된다.
단일 환경에서는 같은 공간에서 선언 정보가 먼저 들어왔다가 실행 중 값이 변경된다.
이렇게 되면 다음 질문에 답하기가 모호해진다.
- 이 식별자는 언제부터 유효했는가?
- 값은 언제 결정되었는가?
{
a: {
declared: true,
initialized: false,
value: undefined
}
}
질문에 답하기 위해 다음과 같이 상태 플래그를 두는 구조를 생각해 볼 수 있다.
이 방식은 단일 구조 안에서도 상태를 구분할 수 있게 해준다.
하지만 이 순간 논리적으로 분리된 두 단계를 하나의 구조에 억지로 집어 넣고 있다는 사실을 인정하는 것과 다름이 없다.
즉, VariableEnvironment는 이 분리를 명시적인 구조로 드러낸 것에 가깝다고 볼 수 있다.
3. 함수 선언은 왜 실행 전에 완성되어 있을까?
function test() {
console.log(fn);
function fn() {}
}
test();
// 실행 결과: function fn() {}
함수 선언은 변수 선언과 달리 실행 전에 이미 완성된 형태로 접근 가능하다.
왜 함수 선언은 값까지 포함된 상태로 실행 전에 접근이 가능할까?
VariableEnvironment 관점에서 보면
선언 수집 단계에서 함수 선언은 완성된 함수 객체 형태로 초기 환경에 바인딩된다.
즉, "초기 환경에 무엇을 넣을 것인가"를 실행 로직과 분리해서 정의할 수 있다.
결론
위의 내용을 통해 VariableEnvironment는 실행 중에 참조되기 위한 구조가 아님을 알 수 있었다.
- 실행이 시작되기 전, 이 컨텍스트에 어떤 식별자들이 존재하는 지를 정의
- 선언과 실행이라는 두 단계를 명확히 분리하기 위한 기준선 역할
그래서 VariableEnvironment는 사용되지 않는 구조가 아니라,
선언 수집이라는 역할을 마친 뒤 실행 단계에서는 사용될 필요가 없도록 설계된 구조라고 볼 수 있다.
이 관점에서 보면 이름 또한 자연스럽다.
Variable: 변할 수 있는
즉, 변화가 시작되기 전의 초기 상태를 정의하는 환경을 뜻한다.
이번 정리를 통해,
단순히 개념을 외우는 것보다 이름과 구조에 담긴 의도를 파악해 보는 과정이 중요하다는 것을 느끼게 되었다.
'공부 > javascript' 카테고리의 다른 글
| 콜백은 왜 신뢰할 수 없는가 - 비동기 제어권의 문제 (1) | 2026.01.16 |
|---|---|
| this는 왜 스코프 규칙을 따르지 않을까 (0) | 2026.01.15 |
| 왜 자바스크립트는 깊은 복사를 기본으로 제공하지 않을까? (0) | 2026.01.12 |
| [JAVASCRIPT] 호출 스택과 이벤트 루프(Call stack and Event loop) (0) | 2024.08.06 |
| [JAVASCRIPT] if문 중첩 제거하기 (0) | 2024.08.06 |