❓실행 컨텍스트란
실행 컨텍스트는 자바스크립트가 동작하기위해 필요한 환경으로, 모든 코드는 실행 컨텍스트를 통해서 실행되고 관리된다.
지난 포스팅에서 알아보았던 스코프, this 또한 모두 실행 컨텍스트 내에서 관리되고 코드의 실행 순서는 실행 컨텍스트 스택에 의해서 관리된다.
1️⃣ 소스코드의 평가와 실행
실행 컨텍스트에 대해서 알아보기 전에 소스코드가 동작하는 과정에 대해서 알아보자.
모든 소스코드는 평가와 실행과정으로 나뉜다. 각각 소스코드 평가와 소스코드 실행은 다음과 같은 역할을 담당한다.
🔘 소스코드 평가: 실행 컨텍스트 생성하고 선언문을 먼저 실행하여 변수 또는 함수 식별자를 생성된 실행 컨텍스트가 관리하는 스코프에 등록
🔘 소스코드 실행: 선언문을 제외한 소스코드가 순차적으로 실행. 즉, 런타임이 시작되고 소스코드가 실행되면서 발생하는 참조를 실행 컨텍스트가 관리하는 스코프 내에서 검색.
2️⃣ 실행 컨텍스트의 동작 과정
const name = "Kim"
const age = 15
function outer() {
const name = "Lee"
const age = 25
function inner() {
const age = 50
console.log(`저는 ${name}이고, ${age}살이에요.`)
}
inner()
}
outer()
위의 코드를 통해 실행 컨텍스트의 동작 과정에 대해서 설명해보록 하겠다.
소스코드가 로드되면 자바스크립트 엔진은 가장 먼저 전역 코드를 평가한다. 생성된 전역 코드의 실행 컨텍스트는 다음과 같다. 세부적인 생성 과정을 살펴보자.
1️⃣ 전역 실행 컨텍스트 생성
가장 먼저 전역 코드를 평가하는 과정에서 실행 컨텍스트가 생성되고, 해당 전역 실행 컨텍스트가 실행 컨텍스트 스택에 푸시된다.
이 때 실행 컨텍스트의 최상위인 전역 실행 컨텍스트는 실행중인 실행 컨텍스트가 된다.
2️⃣ 전역 렉시컬 환경 생성
렉시컬 환경은 환경 레코드와 외부 렉시컬 환경 참조라는 두 가지 컴포넌트로 이루어져 있다.
환경 레코드는 객체 환경 레코드와 선언적 환경 레코드로 이루어져 있는데, 객체 환경 레코드는 var 키워드를 통해 선언한 전역 변수, 함수 선언문으로 정의한 전역 함수, 빌트인 전역 프로퍼티와 빌트인 전역 함수, 표준 빌트인 객체를 관리하고 선언적 환경 레코드는 let, const 키워드를 통해 선언한 전역 변수를 관리한다.
이 때 환경 레코드에서 객체 환경 레코드는 BindingObject 라는 객체와 연결되는데 해당 객체를 통해 전역 코드 평가 과정에서 var 키워드로 선언한 전역 변수와 함수 선언문으로 정의된 전역 함수가 전역 객체의 프로퍼티와 메서드가 된다.
선언적 환경 레코드에서 관리하고 있는 변수의 값이 uninitialzed 인 것은(사실은 값이 아니라 변수에 접근할 수 없다는 표현이다.) 이전 포스팅을 참고하길 바란다.
추가적으로 전역 환경 레코드에는 [[GlobalThisValue]] 라는 내부 슬롯이 존재하고 해당 슬롯에는 this가 바인딩 된다. 전역 코드에서는 일반적으로 this는 전역 객체에 바인딩 된다.
3️⃣ 외부 렉시컬 환경에 대한 참조 결정
외부 렉시컬 환경 참조는 다시 말해, 스코프 체인을 결정하는 것으로 상위 스코프를 가리킨다. 현재 평가하고 있는 소스코드는 전역 코드이기 때문에 가장 상위 스코프이고 참조할 렉시컬 환경이 없기 때문에 null이 할당된다.
4️⃣ 전역 코드 실행
코드에 대한 평가가 이루어졌기 때문에 순차적으로 코드가 실행되고, 실행 중인 실행 컨텍스트 즉, 현재 전역 실행 컨텍스트에서 식별자를 검색한다. name과 age라는 식별자는 전역 실행 컨텍스트의 렉시컬 환경의 환경 레코드에 등록되어 있기 때문에 할당을 진행한다.
전역 코드 평가 이후 순차적인 코드 실행을 통해 outer() 코드에 다다렀다. 해당 outer 함수가 호출되면 전역 코드 실행 흐름에서 outer 함수 내부로 코드의 실행 흐름이 이동한다. 그리고 다음과 같은 순서로 함수 코드 평가가 진행된다.
1️⃣ 함수 실행 컨텍스트 생성
위의 전역 코드 평가시 전역 실행 컨텍스트가 가장 먼저 생성되는 것에서 알 수 있듯이, 함수 코드 평가시에도 가장 먼저 함수 실행 컨텍스트가 생성되고, 실행 컨텍스트 스택에 생성된 함수 실행 컨텍스트가 푸시된다. 따라서 최상위에 있는 outer 함수 실행 컨텍스트가 실행 컨텍스트가 된다.
2️⃣ 함수 렉시컬 환경 생성
함수 렉시컬 환경또한 환경 레코드와 외부 렉시컬 환경 참조의 컴포넌트로 이루어져 있고
전역 환경 레코드와 다르게 함수 환경 레코드는 매개변수, arguments 객체, 함수 내부에서 선언한 지역 변수와 중첩 함수를 관리한다.
외부 렉시컬 환경 참조는 스코프 체인을 결정하는 것이라고 위에서 언급했다. 자바스크립트는 렉시컬 스코프를 따른다. 즉, 함수를 어디에 정의했느냐에 따라 상위 스코프가 결정된다. 자바스크립트 엔진은 함수 객체를 생성할 때 실행 중인 실행 컨텍스트의 렉시컬 환경을 함수 객체의 내부 슬롯인 [[Environment]]에 저장한다.
그렇다면 함수 선언문으로 정의된 outer 함수는 전역 코드가 평가될 때 함수 객체가 생성된 것이고, 당시 실행 중인 실행 컨텍스트는 전역 실행 컨텍스트이므로 outer 함수 객체의 [[Environment]] 슬롯에 저장된 것은 전역 실행 컨텍스트의 렉시컬 환경이다. 따라서 outer 함수의 외부 렉시컬 환경 참조는 전역 렉시컬 환경이다.
함수 환경 레코드 또한 [[ThisValue]] 내부 슬롯에 this가 바인딩되는데, 지난 포스팅에서 살펴보았듯이 this는 호출 방식에 따라 바인딩 되므로 일반 함수로 호출된 outer 함수의 this에는 전역 객체가 바인딩 된다.
3️⃣ 함수 코드 실행
함수 코드에 대한 평가가 이루어졌기 때문에 이제 순차적으로 코드가 실행된다.
이제 inner 함수도 같은 과정을 통해 코드 평가와 코드 실행이 이루어진다. 결과적으로 inner 함수가 실행될 때를 그림으로 보면 다음과 같다.
현재 실행중인 inner 실행 컨텍스트에서 console.log 메서드 호출을 하고있기 때문에 식별자를 inner -> outer -> 전역으로 이어지는 스코프 체인에서 검색한다. 따라서 console 식별자가 존재하는 전역 객체 환경 레코드에서 BindingObject를 통해 전역 객체에서 찾을 수 있고 console 식별자에 바인딩된 객체에서 log 메서드 프로퍼티를 찾는다.
name과 age를 출력해야 하므로 현재 실행중인 inner 실행 컨텍스트에서 name과 age 식별자를 찾아 값을 참조한다. age는 50, name 식별자는 찾을 수 없기 때문에 상위 스코프인 outer 렉시컬 환경에서 name 식별자를 검색하여 Lee를 찾아낸다.
따라서 출력 결과는 "저는 Lee이고, 50살이에요." 임을 알 수 있다.