JavaScript 주요 개념 시리즈 2, 4번째 키워드!
호출 스택 (실행 컨텍스트 스택)과 실행 컨텍스트에 대해 정리하겠습니다.
두 개념은 아주 긴밀한 관련이 있으니 함께 알아두시면 좋을 것 같습니다.
그럼 시작하겠습니다!
23. 실행 컨텍스트
23.1. 소스코드의 타입
ECMAScript 사양은 소스코드를 4가지 타입으로 구분함.
전역 코드, 함수 코드, eval 코드, 모듈 코드 4가지 타입의 소스코드는 실행 컨텍스트를 생성함.
타입에 따라 실행 컨텍스트를 생성하는 과정과 관리 내용이 다름.
23.2. 소스코드의 평가와 실행
실행에 앞서 평가 과정을 거침.
평가 과정에서 실행 컨텍스트를 생성하고 변수, 함수 등의 선언문만 먼저 실행하여 생성된 변수나 함수 식별자를 키로 실행 컨텍스트가 관리하는 스코프에 등록함.
선언문을 제외한 소스코드가 순차적으로 실행됨.(런타임)
실행에 필요한 정보, 즉 변수나 함수의 참조를 실행 컨텍스트가 관리하는 스코프에서 검색하여 취득함.
변수 값의 변경 등 소스코드의 실행 결과는 다시 실행 컨텍스트가 관리하는 스코프에 등록됨.
23.3.실행 컨텍스트의 역할
-
전역 코드 평가
선언문만 먼저 실행 -> 생성된 전역 변수와 전역 함수가 전역 스코프에 등록됨. -> 전역 변수, 전역 함수는 전역 객체의 프로퍼티와 메서드가 됨. -
전역 코드 실행
전역 코드가 순차적으로 실행됨. -> 전역 변수에 값이 할당되고 함수가 호출됨. -> 함수가 호출되면 순차적으로 실행되던 전역 코드의 실행을 일시 중단하고 코드 실행 순서를 변경하여 함수 내부로 진입함. -
함수 코드 평가
매개 변수와 지역 변수 선언문이 먼저 실행되고 생성된 매개변수와 지역 변수가 지역 스코프에 등록됨.
argument 객체가 생성되어 지역 스코프에 등록되고 this 바인딩도 결정됨. -
함수 코드 실행
매개변수와 지역 변수에 값이 할당됨.
함수 코드 실행 과정이 종료되고 함수 호출 이전으로 되돌아가 전역 코드 실행을 계속 함.
코그가 실행되려면 스코프를 구분하여 식별자와 바인딩된 값이 관리되어야 함.
중첩 관계에 의해 스코프 체인을 형성하여 식별자를 검색할 수 있어야 하고, 전역 객체의 프로퍼티도 전역 변수처럼 검색 할 수 있어야함.
함수 호출이 종료되면 함수 호출 이전으로 되돌아가기 위해 현재 실행 중인 코드와 이전에 실행하던 코드를 구분하여 관리해야 함.
이를 위해 모든 코드는 실행 컨텍스트를 통해 실행되고 관리됨.
식별자와 스코프는 실행 컨텍스트의 렉시컬 환경으로 관리하고 코드 실행 순서는 실행 컨텍스트 스택으로 관리함.
23.4. 실행 컨텍스트 스택
생성된 실행 컨텍스트는 스택 자료구조로 관리됨. 이를 실행 컨텍스트 스택이라 함.
실행 컨텍스트 스택은 코드의 실행 순서를 관리함.
소스코드가 평가되면 실행 컨텍스트가 생성되고 실행 컨텍스트 스택의 최상위에 쌓임.
최상위에 존재하는 실행 컨텍스트는 언제나 현재 실행 중인 코드의 실행 컨텍스트이고 이를 실행 중인 실행 컨텍스트라고 함.
23.5. 렉시컬 환경
렉시컬 환경은 식별자와 식별자에 바인딩된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조로 실행 컨텍스트를 구성하는 컴포넌트 임.
실행 컨텍스트는 LexicalEnvironment 컴포넌트와 VariableEnvironment 컴포넌트로 구성됨.
렉시컬 환경은 환경 레코드와 외부 렉시컬 환경에 대한 참조 두 개의 컴포넌트로 구성됨.
- 환경 레코드 : 스코프에 포함된 식별자를 등록하고 등록된 식별자에 바인딩된 값을 관리하는 저장소
- 외부 렉시컬 환경에 대한 참조 : 상위 스코프를 가리킴. 스코프 체인을 구현함.
23.6. 실행 컨텍스트의 생성과 식별자 검색 과정
다음 코드를 참고하여 아래 설명을 이해하자!
var x = 1;
const y = 2;
function foo (a) {
var x = 3;
const y = 4;
function bar (b) {
const z = 5;
console.log(a+b+x+y+z);
}
bar(10);
}
foo(20);
23.6.1. 전역 객체 생성
전역 객체는 전역 코드가 평가되기 이전에 생성됨.
23.6.2. 전역 코드 평가
-
전역 실행 컨텍스트 생성
비어있는 전역 실행 컨텍스트를 생성하여 실행 컨텍스트 스택에 push 함.
-
전역 렉시컬 환경 생성
전역 렉시컬 환경을 생성하고 전역 실행 컨텍스트에 바인딩함.
렉시컬 환경은 환경 레코드와 외부 렉시컬 환경에 대한 참조로 구성됨.
-
전역 환경 레코드 생성
전역 환경 레코드는 전역 변수를 관리하는 전역 스코프, 전역 객체의 빌트인 전역 프로퍼티와 빌트인 전역 함수, 표준 빌트인 객체를 제공 함.
전역 환경 레코드는 객체 환경 레코드와 선언적 환경 레코드로 구성됨.
객체 환경 레코드는 var 전역 변수와 함수 선언문 정의 전역 함수, 빌트인 전역 프로퍼티와 빌트인 전역 함수, 표준 빌트인 객체를 관리하고 선언적 환경 레코드는 let, const 전역 변수를 관리함.
-
객체 환경 레코드 생성
객체 환경 레코드는 BindingObject라고 부르는 객체와 연결된 전역 객체 생성에서 생성된 전역 객체임.
전역 코드 평가 과정에서 var 전역 변수와 함수 선언문 정의 전역 함수는 객체 환경 레코드에 연결된 BindingObject를 통해 전역 객체의 프로퍼티와 메서드가 됨.
-
선언적 환경 레코드 생성
let, const 전역 변수는 선언적 환경 레코드에 등록되고 관리됨.
이들은 선언 단계와 초기화 단계가 분리되어 진행되어 변수 호이스팅이 발생하여도 런타임에 변수 선언문에 도달 전까지 일시적 사각지대에 빠지기 때문에 런타임에 변수 선언문에 도달 전까지 참조 불가함.
-
-
this 바인딩
전역 환경 레코드의 [[GlobalThisValue]] 내부 슬롯에 this(전역 객체)가 바인딩됨.
this 바인딩은 전역 환경 레코드와 함수 환경 레코드에만 존재함.
-
외부 렉시컬 환경에 대한 참조 결정
현재 평가 중인 소스코드를 포함하고 있는 외부 소스코드의 렉시컬 환경, 즉 상위 스코프를 가리킴.
전역 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에는 null이 할당됨.
-
23.6.3. 전역 코드 실행
식별자 결정을 위해 식별자를 검색할 때는 실행 중인 실행 컨텍스트에서 식별자를 검색하기 시작함.
만약 실행 중인 컨텍스트의 렉시컬 환경에서 식별자를 검색할 수 없으면 외부 렉시컬 환경에 대한 참조가 가리키는 렉시컬 환경, 즉 상위 스코프로 이동하여 식별자를 검색함.(스코프 체인의 동작 원리)
23.6.4. foo 함수 코드 평가
-
함수 실행 컨텍스트 생성
함수 실행 컨텍스트는 함수 렉시컬 환경이 완성된 다음 실행 컨텍스트 스택에 Push됨.
-
함수 렉시컬 환경 생성
foo 함수 렉시컬 환경을 생성하고 foo 함수 실행 컨텍스트에 바인딩함.
-
함수 환경 레코드 생성
함수 환경 레코드는 매개변수, argument 객체, 함수 내부에서 선언한 지역 변수와 중첩 함수를 등록하고 관리함.
-
this 바인딩
함수 환경 레코드의 [[ThisValue]] 내부 슬롯에 this가 바인딩됨.
일반 함수로 호출된 foo 함수의 [[ThisValue]]에는 전역 객체가 바인딩됨.
-
외부 렉시컬 환경에 대한 참조 결정
외부 렉시컬 환경에 대한 참조에 foo 함수 정의가 평가된 시점에 실행 중인 실행 컨텍스트의 렉시컬 환경의 참조가 할당됨.
즉, 전역 렉시컬 환경의 참조가 할당됨.JS 엔진은 함수 정의를 평가하여 함수 객체를 생성할 때 현재 실행 중인 실행 컨텍스트의 렉시컬 환경, 즉 함수의 상위 스코프를 함수 객체의 내부 슬롯 [[Environment]]에 정장함.
함수 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 할당된 것은 함수 객체의 내부 슬롯 [[Environment]]에 저장된 렉시컬 환경의 참조임.
-
23.6.5. foo 함수 코드 실행
스코프 체인을 돌며 식별자 결정 진행.
23.6.6. bar 함수 코드 평가
23.6.7. bar 함수 코드 실행
-
console 식별자 검색
console 식별자를 스코프 체인에서 검색함.
스코프 체인은 현재 실행 중인 컨텍스트의 렉시컬 환경에서 시작하여 외부 렉시컬 환경에 대한 참조로 이어지는 렉시컬 환경의 연속임.
console 식별자는 객체 환경 레코드의 BindingObject를 통해 전역 객체에서 찾을 수 있음.
-
log 메서드 검색
console 객체에서 log 메서드를 검색함.
-
표현식 a+b+x+y+z의 평가
식별자는 스코프 체인에서 검색.
-
console.log 메서드 호출
표현식 a+b+x+y+z가 평가 되어 생성한 값 42를 console.log 메서드에 전달하여 호출함.
23.6.8. bar 함수 코드 실행 종료
bar 함수 코드의 실행이 종료됨. -> 실행 컨텍스트 스택에서 bar 함수 실행 컨텍스트가 pop되어 제거됨. -> foo 함수 실행 컨텍스트가 실행 중인 실행 컨텍스트가 됨.
bar 함수 실행 컨텍스트가 제거되었다고 해서 bar 함수 렉시컬 환경까지 즉시 소멸하는 것은 아님.
만약 bar 함수 렉시컬 환경을 누군가 참조하고 있다면 bar 함수 렉시컬 환경은 소멸하지 않음.
23.6.9. foo 함수 코드 실행 종료
foo 함수 코드의 실행이 종료됨. -> 실행 컨텍스트 스택에서 foo 함수 실행 컨텍스트가 pop되어 제거됨. -> 전역 실행 컨텍스트가 실행 중인 실행 컨텍스트가 됨.
23.6.10. 전역 코드 실행 종료
전역 코드의 실행이 종료되고 전역 실행 컨텍스트도 실행 컨텍스트 스택에서 제거되어 실행 컨텍스트 스택에는 아무것도 남지 않음.
23.7. 실행 컨텍스트와 블록 레벨 스코프
선언적 환경 레코드를 갖는 렉시컬 환경을 새롭게 생성하여 기존의 전역 렉시컬 환경을 교체함.
이때 새롭게 생성된 코드 블록을 위한 렉시컬 환경의 외부 렉시컬 환경에 대한 참조는 코드 블록이 실행되기 전의 렉시컬 환경을 가리킴.
코드 블록의 실행이 종료되면 코드 블록이 실행되기 전의 렉시컬 환경으로 되돌아감.
for 문의 변수 선언문에 let 키워들 사용한 for 문은 코드 블록이 반복해서 실행될 때마다 코드 블록을 위한 렉시컬 환경을 생성함.
만약 for문 내에 함수가 있다면 이 함수의 상위 스코프는 for 문의 코드 블록이 생성한 렉시컬 환경이 됨.
이때 함수의 상위 스코프는 for 문의 코드 블록이 반복해서 실행될 때 마다 식별자(for문의 변수 등)의 값을 유지해야 함.
이를 위해 코드 블록이 반복 실행될 때마다 독립적인 렉시컬 환경을 생성하여 식별자의 값을 유지해야 함.(클로저)
이상 호출 스택과 실행 컨텍스트 정리였습니다.
-end-