코드의 실행 순서와 방식, 변수의 접근성, this 키워드의 값 등을 결정하는 요소입니다. 실행 컨텍스트를 이해하면 자바스크립트의 동작 원리를 알 수 있습니다.
Variable Object (VO): 현재 실행 컨텍스트에 관련된 변수와 함수 선언, 매개변수, arguments 객체 등을 포함하는 객체.
Scope Chain: 현재 실행 컨텍스트의 변수 정보를 포함한 환경 레코드와 외부 환경의 변수에 접근하기 위한 참조.
This Binding: 실행 컨텍스트에서 this 키워드의 값을 참조합니다.
실행 컨텍스트는 함수를 호출할 때마다 생성됩니다. 생성된 실행 컨텍스트는 스택에서 관리되며 이를 실행 컨텍스트 스택이라고 합니다. 현재 실행 중인 컨텍스트를 관리하며, 함수 실행이 끝나면 컨텍스트는 스택에서 제거됩니다. 다음 예제를 살펴봅시다
const x = 1;
function foo(){
const y = 2;
function bar(){
const z = 3;
console.log(x + y + z);
}
bar();
}
foo();
위의 코드가 실행되면서 아래와 같이 실행 컨텍스트가 추가되고 제거됩니다.
자바스크립트 엔진은 먼저 전역 코드를 평가하여 전역 실행 컨텍스트를 생성하고 실행 컨텍스트 스택에 푸시합니다. 이때 전역 변수 x와 전역 함수 foo가 전역 실행 컨텍스트에 등록됩니다. 실행 단계에서 x에 값이 할당되고 foo가 호출됩니다.
foo가 호출되면 전역 코드의 실행은 일시 중단되고 코드의 제어권이 foo 함수 내부로 이동합니다. 자바스크립트 엔진은 foo함수 내부의 코드를 평가해 foo 함수 실행 컨텍스트를 생성하고 스택에 푸시합니다. 이때 로컬변수 y와 중첩 함수 bar가 foo 함수의 실행 컨텍스트에 등록됩니다. 이후 실행 단계에서 y에 값이 할당되고 bar가 호출됩니다.
bar함수가 호출되면 foo 함수 코드 실행은 일시 중단되고 코드의 제어권이 bar 함수 내부로 이동합니다. 자바스크립트 엔진은 bar함수 내부의 코드를 평가해 bar 함수 실행 컨텍스트를 생성하고 스택에 푸시합니다. 이때 로컬변수 z가 bar 함수 실행 컨텍스트에 등록됩니다. 이후 실행되면서 z에 값이 할당되고 console.log 호출 후 bar함수는 종료됩니다.
bar 종료 후 제어권이 다시 foo로 돌아오며 bar 함수 실행 컨텍스트가 스택에서 제거됩니다. foo도 실행할 코드가 없으므로 종료됩니다.
foo 종료 후 제어권이 다시 전역 코드로 이동하며 foo 함수 실행 컨텍스트가 스택에서 제거됩니다. 더이상 실행할 코드가 없으므로 종료되어 전역 실행 컨텍스트도 스택에서 제거됩니다.
이처럼 실행 컨텍스트는 코드의 실행 순서를 관리합니다.
소스코드 평가와 실행
- 소스코드 평가 : 변수 함수 등의 선언문을 먼저 실행하여 생성된 변수나 함수를 실행 컨텍스트의 스코프(렉시컬 환경의 환경 레코드)에 추가
- 소스코드 실행 : 선언문을 제외한 소스코드가 순차적으로 실행(런타임). 필요한 정보를 스코프에서 검색해 사용. 값 변경시 스코프에 다시 등록.
식별자와 식별자에 바인딩된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조로 실행 컨텍스트를 구성하는 컴포넌트입니다. 렉시컬 환경은 스코프를 구분하여 식별자를 등록합니다. 환경 레코드와 외부 렉시컬 환경에 대한 참조로 이루어져 있습니다.
환경 레코드(Environment Record)
스코프에 포함된 식별자를 등록하고 등록된 식별자에 바인딩된 값을 관리하는 저장소
외부 렉시컬 환경에 대한 참조
상위 스코프를 가리킨다는 뜻으로 이를 통해 스코프 체인을 구현합니다. 식별자를 검색할때 실행중인 실행 컨텍스트의 렉시컬 환경에서 실별자를 검색할 수 없으면 외부 렉시컬 환경에 대한 참조가 가리키는 환경(상위 스코프)으로 이동하여 식별자를 검색합니다.
빈 전역 실행 컨텍스트 생성 후 스택에 푸시합니다.
전역 렉시컬 환경을 생성하고 전역 실행 컨텍스트에 바인딩합니다.
객체 환경 레코드와 선언적 환경 레코드로 구성됩니다.
BindingObject라 부르는 객체와 연결됩니다. 전역 코드 평가 과정에서 var 키워드로 선언한 전역 변수와 함수 선언문으로 정의된 전역 함수가 포함됩니다. 전역 환경 레코드의 객체 환경 레코드에 연결된 BindingObject를 통해 전역 객체의 프로퍼티와 메서드가 됩니다.
let, const 키워드로 선언한 전역변수가 등록됩니다.
전역 환경 레코드의 내부 슬롯에 this가 바인딩됩니다.
현재 평가중인 소스코드를 포함하는 외브 소스코드의 렉시컬 환경(상위 스코프)을 가리킵니다. 전역 코드를 포함하는 코드는 없으므로 null이 할당됩니다.
foo 함수 실행 컨텍스트를 생성합니다.
foo 함수 렉시컬 환경을 생성하고 foo 함수 실행 컨텍스트에 바인딩합니다.
매개변수, arguments 객체, 지역 변수와 중첩 함수를 등록합니다.
내부에 binding될 객체는 함수 호출 방식에 따라 결정됩니다. 위 예시에선 일반함수이므로 전역 객체를 가리킵니다.
평가된 시점에 실행중인 실행 컨텍스트의 렉시컬 환경의 참조가 할당됩니다. 자바스크립트 엔진은 함수의 정의를 평가해 함수 객체를 생성할 때 현재 실행중인 실행컨텍스트의 렉시컬 환경(상위 스코프)을 함수 객체의 내부에 저장합니다.
클로저는 함수와 그 함수가 선언될 당시의 Lexical Environment를 결합한 것입니다. 이를 통해 외부 함수의 변수에 접근할 수 있습니다. 아래의 코드를 통해 흐름을 알아봅시다.
const x = 1;
function outer(){
const x = 10;
const inner = function() { console.log(x); }
return inner;
}
const innerFunc = outer();
innerFunc();
outer 함수가 있고 내부에서 inner함수를 리턴해주는 클로저입니다. 전역에서 innerFunc
변수를 통해 outer함수를 호출 후 innerFunc
를 실행하고 있습니다.
우선 전역 실행 컨텍스트가 푸쉬됩니다. 그 다음 foo함수를 호출하고 있기 때문에 foo 실행 컨텍스트가 생성됩니다. 이때 함께 생성된 outer의 function record에선 inner function에 대한 참조를 가지고 있고 inner function은 자신의 상위 스코프 즉 outer의 렉시컬 환경을 저장하고 있습니다.
outer함수의 실행이 끝나고 스택에서 제거되었지만 여전히 객체 환경 레코드에 있는 innerFunc
에 의해 inner function object는 참조되고 있고 이 함수객체는 자신의 상위 스코프를 가지고 있습니다. 때문에 클로저는 실행 컨텍스트에서 outer function이 제거되었지만 여전히 그 렉시컬 환경을 참조할 수 있는 것이죠!
모던 자바 스크립트 - 위키북스
https://github.com/Esoolgnah/Frontend-Interview-Questions/blob/main/Notes/important-4/execution-context.md