실행 컨텍스트 뿌시기 part2
var x = 1;
const y = 2;
function foo (a) {
var x = 3;
const y = 4;
console.log(a + x + y);
}
foo(20);
1단계와 비교해서 함수 선언문으로 foo 함수가 추가되었습니다. 함수 내부에는 전역에서 선언한 변수와 동일한 이름의 식별자가 var 키워드와 const 키워드로 선언됐으며, 콘솔로그 또한 함수 내부에 위치해 있습니다. 이때 실행 컨텍스트는 어떻게 형성되는지 살펴보되, 1단계와 중복되는 부분은 차치하고 기술하겠습니다.
👉 실행컨텍스트 뿌시기 Part1.
함수가 추가된 경우에도 실행 컨텍스트의 진행 과정은 1단계와 동일합니다. 전역 객체를 생성하고, 전역 코드 평가 후 전역 코드를 실행합니다. 즉, 함수가 1개든, 2개든, 중첩함수가 존재하며 얼마나 복잡한 코드를 포함하든 전역 실행 컨텍스트 생성 단계에서는 등록할 함수가 존재한다는 것만 인지하면 충분합니다. 그렇다면 위의 코드는 아래와 같이 간략하게 이해해도 좋습니다.
var x = 1;
const y = 2;
function foo (a) {
...
}
foo(20); // foo 함수 호출 직전
함수 내부를 살펴보는 것은 전역 코드가 순차적으로 실행되는 런타임의 마지막에 foo(20) 코드가 실행되며 함수를 호출하려는 순간입니다. 이때 코드의 제어권이 함수 내부로 이동하며 함수 실행 컨텍스트의 평가가 진행됩니다.
이제 전역 소스코드들은 차지하고 함수 내부의 코드만 살펴보면 되는 것이죠. 간략하게 나타내보면 아래와 같습니다.
var x = 3;
const y = 4;
console.log(a + x + y);
그리고 이렇게 살펴보니 1단계에서 봤던 전역 코드와 상당히 유사합니다. 그리고 당연하게도 실행컨텍스트의 구성 및 단계 또한 상당히 유사합니다.
- 실행 컨텍스트를 형성해서 실행 컨텍스트 스택에 푸시한다는 점(함수 실행 컨텍스트의 경우, 렉시컬 환경이 모두 구성된 뒤 푸시됩니다)
- 렉시컬 환경의 환경레코드 컴포넌트에서 식별자들을 등록한다는 점
- 외부렉시컬환경에대한참조에서 외부 렉시컬 환경을 참조하며 스코프 체인을 만든다는 점
위와 같은 큰 틀은 동일하기 때문에 컴포넌트의 용어와 역할을 머릿속에서 한 번 정리하고 이어지는 글을 보면 디테일한 차이점에 대한 이해도 수월하게 할 수 있으실 겁니다.
1. foo 함수 코드 평가
런타임의 마지막에 foo 함수가 호출되며 코드의 제어권이 foo 함수 내부로 이동한 상태입니다.
1.1. 함수 실행 컨텍스트 생성
함수 실행 컨텍스트를 생성합니다.
1.2. 함수 렉시컬 환경 생성
함수 렉시컬 환경을 생성하고, 함수 실행 컨텍스트에 바인딩합니다.
1.2.1. 함수 환경 레코드 생성
함수 환경 레코드에서는 매개변수, arguments 객체, 함수 내부에서 선언한 식별자(지역 변수, 중첩 함수)를 등록하고 관리합니다. 이때 매개변수와 var 키워드로 등록한 변수는 초기화 단계까지 진행되며 undefined가 할당됩니다.
arguments 객체는 함수에 전달된 인수에 해당하는 array 형태의 객체로써, 속성으로 현재 실행 중인 함수를 가리키는 callee, 인수의 개수를 나타내는 length, 인수의 값을 나타내는 인덱스가 있습니다.
1.2.2 this 바인딩
함수 환경 레코드의 [[ThisValue]] 내부 슬롯에 this가 바인딩 됩니다. 이때, foo 함수는 일반 함수로 호출되었으므로 this는 전역 객체를 가리킵니다.
[this 바인딩]
1. 일반 함수 호출 - 전역 객체
2. 메서드 호출 - 메서드를 호출한 객체
3. 생성자 함수 호출 - 생성자 함수가 (미래에) 생성할 인스턴스
4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출 - Function.prototype.apply/call/bind 메서드에 첫 번째 인수로 전달한 객체
1.3. 외부 렉시컬 환경에 대한 참조 결정
foo 함수 정의가 평가된 시점에 실행중인 실행 컨텍스트의 렉시컬 환경의 참조가 할당됩니다. 이를 자세히 살펴보면, foo 함수는 전역 코드에 정의되었기 때문에 함수 내부로 코드의 제어권이 넘어오기 이전의 시점은 전역 코드가 평가되고 있던 시점이었습니다. 그리고 이때의 렉시컬 환경(예제에서는 전역 렉시컬 환경)이 함수의 실행 컨텍스트가 참조할 외부 렉시컬 환경이 되는 것이죠.
2. foo 함수 코드 실행
평가를 마쳤으니 foo 함수의 소스코드가 순차적으로 실행됩니다. 매개변수에는 인수가 할당되고, 변수 할당문이 실행되며 지역변수에 값이 할당됩니다.
그리고 console.log(a+x+y); 코드를 실행하기 위해 console 식별자를 검색합니다. 그런데 console 식별자는 현재 스코프에는 존재하지 않죠. 그래서 외부 렉시컬 환경에 대한 참조가 가리키는 전역 렉시컬 환경에서 검색합니다. BindingObject를 통해 전역 객체에서 console 식별자를 찾았습니다.
console 식별자에 바인딩된 객체에서 log 메서드를 검색합니다. 상속된 프로퍼티가 아닌 console 객체가 직접 소유하는 프로퍼티인 log 메서드를 확인했습니다.
이제 a, x, y 식별자를 검색합니다. 식별자는 현재 실행 중인 실행 컨텍스트의 렉시컬 환경에서부터 검색을 하고, 검색된 식별자를 console.log 메서드에 인수로 전달하여 값을 출력합니다.
참고자료
- 이웅모. 『모던 자바스크립트 Deep Dive』. 위키북스, 2020. <22장. this> <23장. 실행컨텍스트>
- arguments 객체