최상위 스코프인 전역 스코프를 생성한다. var 키워드로 선언된 전역 변수와 함수 선언문으로 정의된 전역 함수를 전역 객체와 연결한다.
함수 코드는 지역 스코프를 생성하고, 지역 스코프를 전역 스코프에서 시작하는 스코프 체인의 일원으로 연결한다.
strict mode에서 자신만의 독자적인 스코프를 생성한다.
모듈별로 독립적인 모듈 스코프를 생성한다.
자바스크립트 엔진은 소스코드를 평가와 실행 과정으로 나누어서 처리한다.
자바스크립트 엔진의 코드 평가 단계다.
선언문만 먼저 실행한다. 즉, 변수와 함수 선언문이 실행 컨텍스트의 전역 스코프에 등록된다. var로 선언한 변수와 함수 선언문으로 정의된 함수는 전역 객체의 프로퍼티와 메소드가 된다.
런타임이 시작되어 전역 코드가 순차적으로 실행된다.
매개변수와 지역 변수 선언문이 실행되고, 생성된 매개변수와 지역 변수가 실행 컨텍스트가 관리하는 지역 스코프에 등록된다. arguments 객체도 등록되고, this 바인딩도 결정된다.
함수 코드가 순차적으로 실행된다. 매개변수와 지역 변수에 값이 할당되고 메서드가 호출된다. 코드 내부의 식별자는 스코프 체인을 통해 검색한다.
코드가 호출되려면 스코프를 구분하여 식별자와 바인딩된 값이 관리되어야 하고, 중첩 관계에 의해 스코프 체인을 형성하여 식별자를 검색할 수 있어야 하고, 전역 객체의 프로퍼티도 전역 변수처럼 검색할 수 있어야 한다.
실행 컨텍스트는 식별자를 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 메커니즘으로, 모든 코드는 이를 통해 실행되고 관리한다.
실행 컨텍스트 스택은 코드의 실행 순서를 관리한다. 코드를 실행하면 코드가 실행되는 시간의 흐름에 따라 실행 컨텍스트 스택에는 실행 컨텍스트가 추가되고 제거된다.
실행 컨텍스트 객체는 총 3가지로 구성된다.
VariableEnvironment : environmentRecord와 outerEnvironmentReference 정보를 가지고 있다. 실행 컨텍스트 초기 생성시 LexicalEnvironment의 스냅샷으로, 변경사항은 반영되지 않는다.
LexicalEnvironment : 실행 초기엔 VariableEnvironment와 구성이 같지만 변경사항이 실시간으로 반영된다.
ThisBinding : this 식별자가 바라보고 있는 대상을 가리킨다.
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);
위 예제로 어떻게 실행 컨텍스트 생성가 코드 실행 결과 관리하는지, 식별자를 검색하는지 알아보자
전역 객체도 Object.prototype을 상속받는다. 즉, 프로토타입 체인의 일원이다.
빈 전역 실행 컨텍스트를 생성하여 실행 컨텍스트 스택에 푸시한다.
전역 렉시컬 환경을 생성하고 전역 실행 컨텍스트에 바인딩 한다.
전역 환경 레코드는 객체 환경 레코드와 선언적 환경 레코드로 구성되어 있다.
전역 환경 레코드의 [[GlobalThisValue]] 내부 슬롯에 this가 바인딩된다.
전역 코드를 포함하는 소스코드는 없으므로 외부 렉시컬 환경에 대한 참조에 null이 할당된다.
변수 할당문이 실행되어 전역 변수 x,y에 값이 할당되고, foo함수가 호출된다. 변수 할당문과 함수 호출문을 실행하려면 변수나 함수 이름이 선언된 식별자인지 확인하는 식별자 결정을 거친다. 식별자 결정을 위해 식별자를 검색할 때 현재 실행중인 실행 컨텍스트에서 식별자를 검색하기 시작한다. 실행 중인 렉시컬 환경에서 식별자를 검색할 수 없으면 외부 렉시컬 환경에 대한 참조가 가리키는 상위 스코프로 이동해서 식별자를 검색한다.
foo 함수가 호출되면 전역 코드의 실행을 일시 중단하고 foo 함수 내부로 코드의 제어권이 이동한다.
foo 함수 실행 컨텍스트를 생성하여 실행 컨텍스트 스택에 푸시한다.
foo 함수 렉시컬 환경을 생성하고 foo 함수 실행 컨텍스트에 바인딩 한다.
함수 환경 레코드는 매개변수, arguments 객체, 함수 내부에서 선언한 지역 변수와 중첩 함수를 등록하고 관리한다.
함수 환경 레코드의 [[ThisValue]] 내부 슬롯에 this가 바인딩된다. foo 함수는 일반 함수로 호출 되었으므로 this는 전역 객체를 가리킨다.
foo 함수는 전역 함수기 때문에 foo 함수 정의는 전역 코드 평가 시점에 평가된다. 즉, 외부 렉시컬 환경에 대한 참조에는 전역 렉시컬 환경의 참조가 할당된다. 자바스크립트는 함수를 어디서 호출했는지가 아니라 어디에 정의했는지에 따라 상위 스코프를 결정한다.
함수 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 할당되는 것은 바로 함수의 상위 스코프를 가리키는 함수 객체의 내부 슬롯 [[Environment]]에 저장된 렉시컬 환경의 참조다.
식별자 결정을 위해 식별자를 검색할 때 현재 실행중인 실행 컨텍스트의 렉시컬 환경에서 식별자를 검색하기 시작한다.
bar 함수가 호출되면 전역 코드의 실행을 일시 중단하고 bar 함수 내부로 코드의 제어권이 이동한다. 렉시컬 환경 생성 과정은 foo 함수와 동일하다.
각 지역변수에 값을 할당한 뒤, console 식별자를 찾아 상위 스코프로 이동한다. console 식별자는 BindingObject를 통해 전역 객체에서 찾을 수 있다.
그 후, console 객체에서 log 메서드를 검색한다.
최종적으로, a + b + x + y + z를 평가하기 위해 각 식별자를 현재 실행 컨텍스트와 상위 스코프의 실행 컨텍스트에서 검색한다. 평가를 완료하면 값을 console.log 메서드에 전달하여 호출한다.
bar 함수 실행 컨텍스트가 팝되어 제거되고 foo 함수 실행 컨텍스트가 현재 실행 중인 실행 컨텍스트가 된다.
bar 함수 실행 컨텍스트가 소멸되어도 bar 함수 렉시컬 환경을 누군가 참조하고 있다면 bar 함수 렉시컬 환경은 소멸하지 않는다.(클로저와 이어지는 개념)
bar 함수 실행 컨텍스트가 팝되어 제거되고 foo 함수 실행 컨텍스트가 현재 실행 중인 실행 컨텍스트가 된다.
foo 함수 실행 컨텍스트가 팝되어 제거되고 전역 실행 컨텍스트가 현재 실행 중인 실행 컨텍스트가 된다. 실행 컨텍스트 스택은 비게 된다.
let x = 1;
if (true) {
let x = 10;
console.log(x); // 10
}
console.log(x); // 1
if 문 코드 블록의 실행이 종료되면 if 문의 코드 블록이 실행되기 이전의 렉시컬 환경으로 되돌린다.
for 문의 변수 선언문에 let 키워드를 사용한 for 문은 코드 블록이 반복해서 실행될 때마다 코드 블록을 위한 새로운 렉시컬 환경을 생성한다.