실행 컨텍스트
실행 컨텍스트, 알아두면 좋은 이유
•
식별자와 값의 바인딩을 관리하는 방식
•
호이스팅 발생 이유
•
클로저 동작 방식
•
이벤트 핸들러와 비동기 처리의 동작 방식
등 실행 컨텍스트를 알면 자바스크립트의 동작 원리를 이해할 수 있다.
모던 자바스크립트 딥 다이브 23장과 ECMAscript 스펙 참고하여 작성.
실행 컨텍스트의 구성
실행 컨텍스트는 '코드 평가 state', 'Function', 'Realm', 'ScriptOrModule' 네 개의 State 컴포넌트로 구성된다.
•
코드 평가 state(code evaluation state): 실행컨텍스트의 코드를 실행, 일시중단, 재개하는 데 필요한 모든 상태
•
function: 실행 컨텍스트가 함수 코드를 평가하는 경우, 컴포넌트의 값은 해당 함수의 객체가 된다. 실행컨텍스트가 스크립트 또는 모듈 코드를 평가하는 경우 값은 null이다.
•
Realm: ECMAscript 리소스에 접근하는 코드에 대한 *Realm Record이다.
•
ScriptOrModule: 모듈 레코드, 스크립트 레코드. 스크립트나 모듈이 없는 경우 값은 null이다.
*Realm: 어떤 코드든 평가되기 전, realm에 연결되어야 한다. realm은 객체, 전역 환경, 전역 스코프에서 로드되는 모든 코드, 기타 state 등으로 구성된다. Tc39 참고
여기에 추가적으로 'Lexical Environment', 'Variable Environment'이라는 두 개의 State 컴포넌트가 있다.
•
렉시컬 환경(Lexical Environment): 실행 컨텍스트 내 코드에서 생성된 식별자 바인딩을 관리하는 환경 레코드를 식별한다.
•
렉시컬 환경은 좀 더 쉽게 말하면 식별자와 식별자에 바인딩된 값을 관리하고 상위 스코프에 대한 참조를 기록하는 자료구조이다. 렉시컬 환경 내의 환경 레코드로 식별자와 값을 관리하고, 외부 렉시컬 환경을 기록하여 상위 스코프를 가리킨다.
실행 컨텍스트는 이러한 state 컴포넌트들을 활용하여 소스코드 실행에 필요한 환경을 제공하고, 실행 결과를 관리한다.
소스 코드 타입
ECMAscript의 소스코드는 4가지 타입으로 나뉜다.
•
전역 코드
•
함수 코드
•
eval 코드
•
모듈 코드
각 코드가 평가될 때마다 실행 컨텍스트가 생성되는데, 타입에 따라 실행 컨텍스트를 생성하는 과정과 관리가 다르다. ECMAscript 스펙에 따르면 전역, 함수, 모듈 타입의 코드에서 함수, 클래스 등 내부 코드는 실행컨텍스트에 포함되지 않는다. 예를 들어 전역에 선언된 함수가 있으면 해당 함수 내부의 코드는 전역 코드에 들어가지 않고 함수 코드로 분리된다.
소스코드 평가와 실행
소스코드 실행은 '소스코드 평가'와 '소스코드 실행'이라는 두 단계로 나뉜다.
소스코드 평가
소스코드 평가 과정에서는 변수, 함수 등 '*선언문'에 해당하는 모든 코드들이 평가된다. 소스코드 평가 과정에서 실행 컨텍스트를 생성하고 변수, 함수 등의 식별자는 실행 컨텍스트의 렉시컬 환경의 **환경 레코드에 등록되어 관리된다. 이렇게 등록한 정보를 가지고 실행 컨텍스트는 코드 실행에 필요한 정보를 제공한다. 그리고 실행이 끝나면 해당 코드의 실행 결과는 환경 레코드에 다시 등록된다.
*함수 표현식이 호이스팅되지 않는 이유기도 하다. 변수에 할당하지 않는 선언문 같은 경우는 실행 전에 먼저 평가되어 호이스팅되지만, 함수 표현식의 경우 먼저 평가되지 않고 나중에 실행되기 때문에 호이스팅되지 않는다.
**환경 레코드: 포스팅 참고
소스코드 실행
식별자와 스코프는 실행 컨텍스트의 렉시컬 환경으로 관리하고 코드 실행 순서는 실행 컨텍스트 스택(call stack이라고도 한다)으로 관리한다.
모던 자바스크립트 딥 다이브 23-03 예제
const x = 1;
function foo() {
const y = 2;
function bar() {
const z = 3;
console.log(x + y + z);
}
bar();
}
foo();
JavaScript
복사
앞서 실행 컨텍스트의 구성을 설명하며 code evaluation state는 코드의 실행, 일시 중단, 재개를 하는 데 필요한 모든 상태를 제공한다고 했다. 실행 컨텍스트는 전역 코드를 실행하다가 함수가 호출되면 전역 코드를 실행 중단한 후, 함수 코드를 실행한다.
코드를 실제로 실행하는 *agent는 실행 컨텍스트를 하나밖에 갖지 못한다. 그러므로 함수가 호출되면 실행중인 컨텍스트를 멈추고 새로운 실행 컨텍스트를 스택에 넣어 실행한다. 실행 컨텍스트의 스택은 이렇게 하나씩 실행되는 실행 컨텍스트들을 트래킹하기 위해 사용한다. 스택 자료구조의 특성으로 인해 현재 실행중인 컨텍스트(running execution context)는 스택의 맨 위에 놓인다.
*agent: 실행 컨텍스트의 집합, 실행 컨텍스트 스택, running execution context, 에이전트 레코드 및 실행 중인 스레드의 집합으로 구성된다. 실행 중인 스레드를 제외하고 agent의 구성 요소는 해당 에이전트에만 속한다.
실행 컨텍스트의 생성, 식별 과정
전역 객체 생성
전역 코드가 평가되기 전에 전역 객체가 생성된다. 전역 객체는 빌트인 전역 프로퍼티, 빌트인 전역 함수, 표준 빌트인 객체를 포함한다. 환경에 따라 WebAPI 또는 *호스트 객체를 포함한다.
*호스트 객체: window, global 등 호스트 환경에서 정의된 객체.
전역 코드 평가
•
전역 실행 컨텍스트 생성
◦
전역 렉시컬 환경 생성
▪
외부 렉시컬 환경 참조
▪
▪
전역 환경 레코드
•
객체 환경 레코드 생성: var 키워드에 의해 생성된다.
•
선언 환경 레코드: let, const 키워드에 의해 생성된다.
전역 코드의 평가 과정을 보면 아래와 같다.
실행 컨텍스트는 렉시컬 환경과 변수 환경으로 구성된다. 그리고 렉시컬 환경은 환경 레코드와 외부 렉시컬 환경 참조(OuterLexicalEnvironmentReference)로 구성된다.
전역 실행 컨텍스트가 먼저 생성되면 그 안의 전역 렉시컬 환경이 생성된다. 렉시컬 환경은 외부 렉시컬 환경을 참조하며 전역 환경 레코드의 [[GlobalThisValue]] 내부 슬롯에 전역객체와 this가 바인딩 된다. 전역 코드에서 this를 참조하면 전역 환경 레코드의 [[GlobalThisValue]] 내부 슬롯에 바인딩된 객체를 반환한다.
전역 환경 레코드는 이전 포스팅 '환경 레코드'에서 설명한 대로 이론상으로는 싱글 레코드지만, var 키워드와 let, const 키워드로 선언한 전역 변수를 구분하여 관리하기 위해 실제로는 객체 환경 레코드와 선언 환경 레코드로 구성된다.
객체 환경 레코드에서는 var 키워드로 선언한 전역 변수, 함수 선언문으로 정의한 전역 함수, 빌트인 전역 프로퍼티와 빌트인 전역 함수, 표준 빌트인 객체 등을 관리하고 선언적 환경 레코드는 let, const 키워드로 선언한 전역 변수를 관리한다.
let const로 선언한 전역 변수는 전역 객체 프로퍼티가 되지 않고 블록 스코프에 속한다. 전역 코드를 실행하다가 블록문 내에서 let, const 키워드의 변수 선언문을 만나면 선언 환경 레코드를 갖는 렉시컬 환경을 새로 생성해 기존 전역 렉시컬 환경을 교체한다.
// 브라우저에서 해보세요
var testA = '1'
window.testA // "1"
const constA = '2'
window.constA // undefined
JavaScript
복사
전역 코드 실행
코드를 실행하려면 변수, 또는 함수명이 이미 선언된 식별자인지 확인해야 한다. 코드 평가 과정에서 기록되지 않은 식별자는 실행 컨텍스트에서 참조할 수 없어 코드를 실행할 수 없다. 또, 같은 이름을 가진 식별자를 구하기 위해서 어느 스코프를 참조해야할지도 결정해야 한다.
그러므로 실행 컨텍스트는 코드 실행에 필요한 식별자를 검색한다. 먼저 현재 실행중인 running execution context의 렉시컬 환경의 환경 레코드에서 식별자를 찾는다. 만약 없다면 외부 렉시컬 스코프 참조를 통해 상위 스코프로 이동하여 다시 식별자를 검색한다. 만약 최상위 스코프인 전역 렉시컬 환경에서도 식별자를 찾지 못하면 reference error가 발생한다.
References
•
모던 자바스크립트 딥 다이브 23장
•
ECMAscript 11장 https://tc39.es/ecma262/#sec-types-of-source-code