[JS] 모던 Javascript Deep Dive 23장 - 실행 컨텍스트
업데이트:
이 파트를 이해하면 자바스크립트에서 호이스팅이 발생하는 이유, 식별자 바인딩 관리 방식, 클로저의 동작 방식 등등.. 을 이해할 수 있다고 한다.
실행 컨텍스트 파트가 어렵다는 말을 많이 들어서 두렵지만 자바스크립트의 동작 원리를 담고 있는 핵심 개념이니 열심히 달려보자 🏃🏻♀️
[01] 소스코드의 타입/평가/실행
소스코드의 타입
우리가 사용하는 코드는 실행 컨텍스트를 생성하는 과정과 관리 내용에 따라 4가지 타입으로 나뉜다.
전역 코드 | 전역에 존재하는 소스코드, 전역에 정의된 함수, 클래스 등의 내부 코드는 포함 x |
---|---|
함수 코드 | 함수 내부에 존재하는 소스코드, 함수 내부에 중첩된 함수, 클래스 등의 내부 코드는 포함x |
eval 코드 | 빌트인 전역 함수인 eval 함수에 인수로 전달되어 실행되는 소스코드 |
모듈 코드 | 모듈 내부에 존재하는 소스코드, 모듈 내부의 함수, 클래스 등의 내부 코드는 포함 x |
위 네가지 중 일반적으로 실행 가능 코드는 전역코드와 함수 코드이다.
소스코드의 평가와 실행
var x;
x = 1;
이 코드는 아래 2개 과정으로 나누어 처리된다.
- 평가 과정
var x;
가 실행됨 ⇒ 변수 식별자 x는 실행 컨텍스트가 관리하는 스코프에 등록 ⇒ undefined로 초기화
- 실행 과정
var x;
는 이미 평가 과정에서 실행이 완료되었으니 넘어간다.
x=1;
가 실행 ⇒ x 변수가 선언되어 있는지 확인(실행 컨텍스트가 관리하는 스코프에 x가 있는지) ⇒ x 변수가 스코프에 등록되어 있다면 값을 할당 ⇒ 할당 결과를 실행 컨텍스트에 등록
[02] 실행 컨텍스트란?
☝🏻 실행 컨텍스트는 코드가 실행되기 위해 필요한 환경을 제공하고 코드의 실행 결과를 실제로 관리하는 영역이다.
코드가 실행되기 위해 필요한 환경을 제공하는 것은 렉시컬 환경에서 담당하고, 코드 실행 순서, 실행 결과를 관리하는 것은 실행 컨텍스트 스택이라 한다.
먼저 실행 컨텍스트 스택은 어떠한 내부 구조를 갖고 있는지 살펴보자.
실행 컨텍스트 스택
실행 컨텍스트 스택은 말이 길고 복잡하니 설명할 때는 그냥 줄여서 스택이라 표현하겠다 !
const x = 1;
function foo() {
const y = 2;
function bar() {
const z = 3;
console.log(x + y + z);
}
bar();
}
foo();
위 코드가 실행되면 어떤 순서로 스택에 추가되고 제거되는지 알아보자.
-
전역 코드의 평가와 실행
전역 실행 컨텍스트 생성 후 스택에 push, x 변수와 foo 함수가 전역 실행 컨텍스트에 담김
-
foo 함수 코드의 평가와 실행
foo 함수 실행 컨텍스트 생성 후 스택에 push , foo 함수의 내부 변수 y와 내부 함수 bar()가 foo 함수 실행 컨텍스트 안에 담김
-
bar 함수 코드의 평가와 실행
bar 함수 실행 컨텍스트 생성 후 스택에 push, bar 함수 내부의 z 변수가 bar 함수 실행 컨텍스트 안에 담김 ⇒ bar 함수 코드 실행되어 z에 값 할당/ console.log 메서드 호출⇒ bar 함수 종료
-
foo 함수 코드로 복귀
bar 함수 실행 컨텍스트를 스택에서 팝하여 제거⇒ foo 함수에는 실행할 코드가 없으므로 종료
-
전역 코드로 복귀
foo 함수 실행 컨텍스트를 스택에서 팝하여 제거 ⇒ 실행할 전역 코드가 없으므로 종료
위와 같은 과정을 거쳐 실행 컨텍스트 스택이 소스코드의 실행 순서를 관리한다.
☝🏻 스택의 최상위에 존재하는 실행 컨텍스트는 실행중인 실행 컨텍스트라 한다.
렉시컬 환경
소스코드에서 렉시컬 환경은 스코프와 식별자를 관리하는 것을 담당한다.
그런데 렉시컬 환경이 너무 추상적인 말이라서 책에서 나온 렉시컬 환경을 정의할 수 있는 말들을 정리해보았다.
🥕 렉시컬 스코프의 실체
🥕 식별자, 식별자에 바인딩된 값, 상위 스코프에 대한 참조를 기록하는 곳
🥕 스코프를 구분하여 식별자를 등록하고 관리하는 저장소 역할
렉시컬 환경은 또 두개의 컴포넌트로 나뉜다고 한다. 아마도 내부함수와 외부함수의 변수/함수들을 분리해서 저장해둘 공간이 필요해서인 것 같다.
- 환경 레코드(Environment Record) : 변수와 값이 저장
- 외부 렉시컬 환경에 대한 참조(Outer Lexical Environment Reference) : 외부 함수의 변수나 함수 저장
[03] 실행 컨텍스트의 생성과 식별자 검색 과정
이제 실행 컨텍스트가 무엇인지 어느정도 파악했으니 코드 실행 과정을 순서대로 파헤쳐 보겠다.
분석해볼 예시 코드이다.
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);
코드 실행 과정을 책에서는 그림으로 다뤘지만 크롬 개발자 도구를 이용하여 실행 컨텍스트를 더 쉽게 이해해보려고 한다.
1) 전역 객체 생성
전역 코드가 평가되기 이전에 전역 객체가 먼저 생성된다.
코드를 평가하기도 이전에 window가 생성되어 있다는 의미이다.
2) 전역 코드 평가
소스코드가 로드되면 아래 순서대로 전역 코드의 평가가 진행된다.
- 전역 실행 컨텍스트 생성
- 전역 렉시컬 환경 생성
- 전역 환경 레코드 생성
- 객체 환경 레코드 생성
- 선언적 환경 레코드 생성
- this 바인딩
- 외부 렉시컬 환경에 대한 참조 결정
- 전역 환경 레코드 생성
1. 전역 실행 컨텍스트 생성
비어있는 전역 실행 컨텍스트를 생성하고 실행 컨텍스트 스택에 푸시한다.
2. 전역 렉시컬 환경 생성
전역 렉시컬 환경을 생성하고 전역 실행 컨텍스트에 바인딩한다.
2-1. 전역 환경 레코드 생성
전역 환경 레코드는 var 키워드와 let,const를 구분하기 위해 또 두가지로 나뉜다.
- 객체 환경 레코드 : var 키워드로 선언된 변수와 함수 선언문으로 정의된 전역함수 저장
- 선언적 환경 레코드 : let, const 키워드로 선언한 전역 변수와 함수 표현식 저장
크롬 브라우저에서 두번째 줄 코드에 breakpoint를 걸어두고 오른쪽의 Scope, Call Stack을 보자.
const로 선언한 y는 Script 안에 undefined값이 할당된 채로 들어있다. 하지만 초기화가 이루어지지 않아 TDZ에 빠져있기 때문에 참조할 수는 없다.
var로 선언한 x변수와 함수 선언문으로 정의된 foo() 함수는 Global 스코프를 펼쳐보면 그 안에 들어있다.
2-2 this 바인딩
전역 환경 레코드의 [[GlobalThisValue]] 내부 슬롯에 this가 바인딩된다.
전역 코드에서 this를 참조하면 [[GlobalThisValue]] 내부 슬롯에 바인딩되어 있는 객체가 반환된다.
크롬 브라우저에서 this를 출력해봤더니 window가 나왔다.
2-3 외부 렉시컬 환경에 대한 참조 결정
내용이 너무 길어서 순간 까먹었는데 지금 전역 객체를 평가하는 중이다. 그렇기 때문에 외부 렉시컬 환경은 없으니 null이 할당된다.
3) 전역 코드 실행
드디어 코드가 실행되었다. 이제 런타임에 들어왔다고 보면 된다.
처음에는 var x = 1;
코드가 실행될 것이다.
우리는 이미 x를 객체 환경 레코드에 등록해두었으므로 자바스크립트 엔진은 x 식별자가 어디에 있는지 검색하기 시작한다. 같은 방법으로 const y = 2;
도 y 식별자를 검색한다.
y는 const로 선언했기 때문에 Script라는 스코프 안에 들어있어서 Global 스코프에 굳이 안가도 찾을 수 있다.
x는 var로 선언했기 때문에 Global 스코프 안에서 찾은 뒤 1을 할당해주었다.
4) foo 함수 코드 평가
코드를 실행하다보면 foo(20);
함수가 호출된다. 그럼 전역 코드가 일시 중단되고 foo 함수를 평가하기 시작한다. 함수 코드 평가에도 전역 코드 평가와 같은 단계가 거친다.
- 함수 실행 컨텍스트 생성
- 함수 렉시컬 환경 생성
- 함수 환경 레코드 생성
- this 바인딩
- 외부 렉시컬 환경에 대한 참조 결정
1. 함수 실행 컨텍스트 생성
이 그림을 다시 가져왔다. 현재 노란색 박스 단계까지 온 것이다.
foo 함수 실행 컨텍스트가 생성되고 최상위 블록이므로 현재 실행 중인 실행 컨텍스트가 된다.
크롬 브라우저의 Call Stack 칸에도 foo 함수 실행 컨텍스트가 생긴 것을 볼 수 있다.
2. 함수 렉시컬 환경 생성
foo 함수 렉시컬 환경을 생성하고 foo 함수 실행 컨텍스트에 바인딩한다.
2-1. 함수 환경 레코드 생성
매개변수 a, arguments 객체, 함수 내부의 지역 변수, 중첩 함수가 함수 환경 레코드에 등록된다.
위에서 크롬 개발자 도구 Scope를 보면 없던 Local 스코프가 생겨난 것을 확인할 수 있다.
2-2. this 바인딩
[[ThisValue]] 내부 슬롯에 this가 바인딩된다.
foo 함수는 일반함수로 호출되었으므로 this는 전역 객체를 가리킨다.
결국 [[ThisValue]] 내부 슬롯에 전역 객체 Window가 바인딩된다.
2-3. 외부 렉시컬 환경에 대한 참조 결정
자바스크립트가 함수가 정의된 곳에서 상위 스코프를 결정하는 이유를 설명할 수 있는 단계이다.
foo 함수의 외부 렉시컬 환경은 전역 렉시컬 환경이다. foo 함수의 상위 스코프는 전역 스코프라는 말과 똑같다.
그렇기 때문에 외부 렉시컬 환경에 대한 참조에는 전역 렉시컬 환경의 참조가 할당된다.
5) foo 함수 코드 실행
foo 함수를 실행시키면 foo 함수 내부 변수인 x와 y에 차례로 값이 잘 할당된다. 그리고 마지막에 bar(10);
함수가 호출된다.
6) bar 함수 코드 평가
foo() 함수 내부에서 bar(10)
함수가 호출되었다. foo 함수의 코드 진행은 잠시 중단되고 bar 함수를 평가하기 시작한다.
앞서 다룬 foo 함수 코드를 평가하는 과정과 같다.
7) bar 함수 코드 실행
매개변수에 인수가 할당되고 지역 변수 z에도 값이 잘 할당된 것을 확인할 수 있다.
console.log 메서드까지 잘 출력이 되었다.
8) bar 함수 코드 실행 종료
bar 함수의 코드가 종료되면 실행 컨텍스트 스택에서 bar 함수가 pop되어 제거된다.
하지만 생성되었던 렉시컬 환경은 bar함수를 누군가 참조하는 한 소멸되지는 않는다고 한다.
오른쪽 Call Stack을 보면 파란색 화살표가 실행 중인 실행 컨텍스트를 가리키는 것이다.
이제 bar 함수 실행 컨텍스트가 제거되어 foo함수를 가리키고 있다.
9) foo 함수 코드 실행 종료
foo 함수도 종료되면 전역 실행 컨텍스트가 실행중인 실행 컨텍스트가 된다.
10) 전역 코드 실행 종료
전역 코드의 실행도 종료되면 실행 컨텍스트 스택에서 팝되어 스택에 아무것도 남아있지 않게 된다.
맺음말 ..
이번 챕터는 코드보다 글이 많아서 난독증이 올 것만 같았다.
그래서 여러 강의들도 찾아보고 이해해보려고 했는데 생활코딩의 execute context 강의가 도움이 되었다.
실제 개발자 도구를 사용하여 코드 실행 과정을 눈으로 확인하니 환경 레코드와 실행 컨텍스트 스택이 더 이해가 잘되었다. 위 강의를 들어보고 책을 읽는 것을 추천한다… !
댓글남기기