코드 실행에 필요한 정보를 담은 실행 컨텍스트
Execution Context : EC
실행 컨텍스트(Execution Context)는 자바스크립트 코드를 실행하기 위해 필요한 정보를 저장하고 제공하는 환경입니다. 기본적으로 스코프의 정보를 담은 환경을 말하며, 새로운 스코프가 생성될 때마다 실행 컨텍스트도 생성되면서 스택 메모리에 쌓이게 됩니다. 그리고 스택이 쌓일 때마다 새로운 컨텍스트에게 코드 실행에 대한 제어권을 넘기며, 실행이 종료된 이후에는 소멸됨과 동시에 제어권을 다시 상위 컨텍스트에게 돌려줍니다. 실행 컨텍스트는 자바스크립트의 동작 원리를 담고 있는 핵심 개념이기 때문에 정말 중요합니다.
실행 컨텍스트를 잘 이해하면, 스코프, 호이스팅, 클로저 등의 원리도 모두 이해할 수 있습니다. 😱
다음의 코드와 그림을 통해 실행 컨텍스트가 스택에 쌓이는 과정을 알아보도록 합시다.
console.log('전역 컨텍스트');
function foo() {
console.log('foo 컨텍스트');
}
function bar() {
foo();
console.log('bar 컨텍스트');
}
// 전역 컨텍스트
// foo 컨텍스트
// bar 컨텍스트
bar();
위 그림은 실행 컨텍스트가 스택에 쌓이는 과정을 간단하게 나타낸 것이며, 각 컨텍스트의 내부 구조는 여러 가지로 구성되어 있습니다.
컨텍스트 구성 요소
실행 컨텍스트가 생성될 때 내부에는 Lexical Environment(LE)와 Variable Environment(VE)라는 컴포넌트가 생성되며, 이들은 Environment Record(ER)라는 형태로 구성됩니다. 이번에도 예제 코드와 함께 실행 컨텍스트의 컴포넌트가 어떻게 채워지는지, 간단한 그림으로 먼저 알아보겠습니다.
function foo() {
let a = 1;
const b = 2;
var c = 3;
console.log(a + b + c);
}
foo();
foo 함수를 호출하면 위 그림과 같이 실행 컨텍스트가 생성되며, 내부에는 호출에 대한 정보를 담고 있습니다. 레코드의 각 필드는 함수 내부에 선언한 식별자와 this 바인딩, 그리고 외부 환경 레코드(상위 렉시컬 스코프)에 대한 정보를 값으로 갖습니다. 이렇듯 외부 환경 레코드 정보가 담겨있기 때문에 언제든 상위 렉시컬 스코프에 대한 정보를 알 수 있습니다. 이것이 스코프 체이닝이며, ECMAScript에서 사용하는 공식 용어로는 Lexical Nesting Structure라 합니다.
추상 클래스 ER
환경 레코드(ER)는 일종의 추상 클래스로서 이를 세부적으로 구현하는 하위 ER들이 존재합니다.
Declarative Environment Record (선언적 환경 레코드)
- 렉시컬 스코프 내에 선언된 식별자를 컨텍스트에 바인딩합니다.
- 변수, 상수, 클래스, 모듈 등이 포함됩니다.
- 화살표 함수가 아닌 경우 this를 바인딩합니다.
- 상속되었다면 super로 상위 클래스 정보를 바인딩합니다.
Object Environment Record (객체 환경 레코드)
- Binding Object라는 객체의 프로퍼티들을 식별자로 바인딩합니다.
- with문처럼 동적으로 스코프를 변경하는 경우, Binding Object로 this값을 제공합니다.
// with로 생성된 객체 환경 레코드는
// Binding Object를 암묵적으로 제공하므로
// 블록 내에서 프로퍼티에 바로 접근 가능
with (Math) {
// 원래는 Math.pow(2, 2);
console.log(pow(2, 2));
}
Global Environment Record (전역 환경 레코드)
- 최상위 스코프에 선언된 식별자와 전역 객체를 바인딩합니다.
- 이론상으로는 하나의 레코드이지만, 위의 두 레코드(Declarative ER & Object ER)로 구성됩니다. 전역 환경 레코드의 Object ER은 Binding Object를 통해 내장 전역 객체와 var 키워드로 선언된 변수 등을 참조 가능하며 이것이 window 전역 객체입니다. 그리고 나머지 식별자들에 대한 바인딩은 Declarative ER에 저장되며 이를 전역 변수라 부릅니다.
이렇게 다양한 환경 레코드에 의해 컨텍스트 내부가 채워집니다. 👏
컨텍스트 생성 과정
이제 실행 컨텍스트의 내부가 어떻게 구성되는지 알았으니, 앞에서 언급한 컴포넌트와 레코드를 모두 그려가며 과정을 살펴보도록 합시다. 생성 과정 자체는 크게 어렵지 않으니 조금만 참고 화이팅! 🔥
다음의 코드를 예제로 하나 둘 그려보겠습니다.
function foo() {
let x = 1;
function bar() {
const y = 2;
var z = 3;
// 6
console.log(x + y + z);
}
bar();
}
foo();
전역 컨텍스트 생성
자바스크립트 코드가 실행되면 가장 먼저 전역 컨텍스트가 스택 메모리에 추가되며, Global ER이 생성됩니다. Global ER은 this와 outerEnv 필드만 설정된 상태이고, 이후 자바스크립트 엔진의 소스 평가 과정에 의해 foo 함수에 대한 정보를 바인딩하여 런타임에 실행합니다.
전역 컨텍스트의 상위 컨텍스트는 존재하지 않기 때문에, outerEnv의 값은 null입니다.
전역 컨텍스트의 this는 브라우저 환경이라면 window를, Node 환경이라면 globalThis를 가리킵니다.
foo 컨텍스트 생성
foo 함수를 호출하면 foo 컨텍스트가 생성되어 스택에 쌓이고, 코드 실행에 대한 제어권을 가져옵니다. 그리고 전역 컨텍스트를 생성할 때와 마찬가지로 outerEnv 필드의 값이 정해지는데, 상위 렉시컬 스코프가 Global ER이기 때문에 이로 정해집니다. 하지만 this는 초기에는 uninitialized 상태로 있는데, 이 필드는 식별자를 하나씩 바인딩하는 소스 평가 과정에 순차적으로 결정됩니다.
자바스크립트 엔진의 소스 평가 과정에서 변수 x라는 식별자가 바인딩되고, bar라는 함수의 식별자와 몸통이 바인딩됩니다. 그리고 this는 this 바인딩 규칙에 의해 전역 객체를 가리키도록 동적으로 결정됩니다. 이렇게 평가 과정이 끝나면 런타임 과정에 변수 x에 값을 할당하고, bar 함수를 실행합니다.
그림에서 this의 값이 undefined로 되어 있는데, 이를 출력하면 전역 객체를 가리키고 있는 것을 확인할 수 있습니다. 하지만 엄격 모드(Strict Mode)에서 실행하면, undefined가 출력됩니다.
bar 컨텍스트 생성
bar 컨텍스트 역시 호출과 함께 생성됩니다. 그리고 이 안에는 두 가지 변수를 각각 const와 var 키워드로 만들었는데, const로 선언한 변수의 식별자는 LE에, var로 선언한 변수의 식별자는 VE에 바인딩됩니다. var는 VE, 나머지는 LE로 기억하시면 좋습니다. bar 함수도 평가 과정에 식별자가 모두 바인딩되고, 런타임 과정에 값이 할당되며 마지막에 console.log 메서드를 실행합니다.
console.log 메서드가 참조하는 변수 중 y와 z는 자신의 렉시컬 스코프 안에 있기 때문에 바로 참조 가능하지만, x는 모르기 때문에 outerEnv를 타고 상위 렉시컬 스코프로 이동해 값을 찾습니다.
var로 선언된 변수는 평가 과정에서 식별자의 등록과 함께 undefined로 초기화되기 때문에 선언 이전에도 참조가 가능합니다. 하지만 let 또는 const로 선언된 변수는 평가 과정에서 식별자는 등록하지만, TDZ라는 영역의 존재로 런타임 과정에서 실제 할당문을 만나기 전까지 초기화할 수 없습니다. 모두 호이스팅이 일어나지만 키워드에 따라 약간의 차이가 있습니다. 👏
글을 마무리 하며
본문의 서두에서 잠깐 언급한 것처럼 실행 컨텍스트는 자바스크립트 중요한 개념들의 동작 원리를 담고 있는 핵심 개념입니다. 실행 컨텍스트를 이해하면 렉시컬 스코프가 무엇인지, this는 언제 어떻게 바인딩되는 것인지, 클로저는 어떻게 스코프를 기억하는지 등을 알 수 있습니다. 최대한 그림을 그려가며 설명을 도왔지만, 글솜씨가 부족하고 최대한 짧은 글에 많은 내용을 넣으려니 제대로 녹이지 못 한 것 같네요 ㅠㅠ
나중에 공부를 더 해서 최신화를 하게 된다면, 더 깔끔하게 정보를 공유해 보겠습니다. 😀
References
'🌈 기술스택 > JavaScript' 카테고리의 다른 글
자바스크립트에 동시성을 부여하는 이벤트 루프 (0) | 2021.10.07 |
---|---|
연속으로 발생하는 이벤트를 제어하는 방법 (0) | 2021.10.07 |
자기 자신을 가리키는 값 this (0) | 2021.10.03 |
자바스크립트 코드의 유효 범위 Scope (0) | 2021.10.03 |
얕은 복사 & 깊은 복사 (0) | 2021.10.03 |
댓글
이 글 공유하기
다른 글
-
자바스크립트에 동시성을 부여하는 이벤트 루프
자바스크립트에 동시성을 부여하는 이벤트 루프
2021.10.07 -
연속으로 발생하는 이벤트를 제어하는 방법
연속으로 발생하는 이벤트를 제어하는 방법
2021.10.07 -
자기 자신을 가리키는 값 this
자기 자신을 가리키는 값 this
2021.10.03 -
자바스크립트 코드의 유효 범위 Scope
자바스크립트 코드의 유효 범위 Scope
2021.10.03