결국 올 것이 왔다. 실행 컨텍스트.
강의도 여러 번 보고 브라우저 디버거를 통해 call stack을 확인하면서 전체적인 흐름을 읽혔다. 자바스크립트 코드를 보고 실행 컨텍스트를 그려보라고 하면 해볼 수 있는 정도의 이해는 갖추었다. 다만, 모던 자바스크립트 23장 실행 컨텍스트에 나온 자세한 내용들과 용어들을 한 번에 이해하고 넘어가는 것은 무리라고 판단하기 때문에 이번 포스팅에선 실제 코드를 바탕으로 실행 컨텍스트가 어떤 흐름으로 진행되는지에 대해 가볍게 정리하고 넘어가려고 한다. 생활코딩 유튜브 강의가 많이 도움되었다.
실행 컨텍스트는 코드가 실행되고 관리되는 환경을 의미한다. 자바스크립트 엔진은 코드를 실행할 때 실행 컨텍스트를 생성하며, 이 컨텍스트는 Call Stack에 쌓인다.
자바스크립트 엔진은 소스코드를 두 개의 과정, 평가와 실행으로 나누어 처리한다.
<script>
n0 = 'n0'; // 1️⃣
var v0 = 'v0';// 2️⃣
let l0 = 'l0';
const c0 = 'c0';// 3️⃣
console.log(v0, n0, l0, c0);
console.log(window.v0, window.n0, window.l0, window.c0);
function fn2() {
n2 = 'n2';
console.log(n0, n1, n2);
var v2 = 'v2';
console.log(v0, v2);
// console.log(v1) // Error 7️⃣
let l2 = 'l2';
console.log(l0, l2);
// console.log(l1);
const c2 = 'c2;';
console.log(c0, c2);
// console.log(c1);
}// 9️⃣
function fn1() {
n1 = 'n1';
var v1 = 'v1';// 5️⃣
let l1 = 'l1';
const c1 = 'c1';
fn2();// 6️⃣
}// 9️⃣
fn1();// 4️⃣
console.log(n2);
// console.log(v2, l2, c2);
</script>
코드가 실행되기 앞서 평가 과정에서 실행 컨텍스트가 생성되고 전역의 변수, 함수 등의 선언문만 먼저 실행하여 실행 컨텍스트가 관리하는 스코프에 등록한다. 현재 Call Stack에는 Global 실행 컨텍스트(브라우저 디버거에선 (anonymous)라고 표시된다)가 쌓여있고
이후 전역 코드의 실행과 함수 코드 평가 및 실행은 아래와 같다.
1️⃣ 변수를 선언할 때 아무 키워 없이 식별자 = 값; 형태로 입력하면 변수는 Global 스코프에 등록된다. (-> 전역 변수)
2️⃣ var 키워드도 위와 같이 Global 스코프에 등록된다.(함수 스코프에서 차이점 유의)
3️⃣ let과 const로 선언한 변수는 위 1️⃣, 2️⃣와 같이 전역 변수지만 Global 스코프에 등록되는 것이 아니라 (위 코드에선) script 스코프에 등록된다. 이것을 조금 더 정확히 말하자면 전역 실행 컨텍스트는 아래와 같은 형태로 구성되어 있는데
전역 실행 컨텍스트 > 전역 렉시컬 환경 > 전역 환경 레코드
전역 환경 레코드는 객체 환경 레코드와 선언적 환경 레코드로 나뉜다. 여기서 var 키워드로 선언한 전역 변수는 객체 환경 레코드에 let, const로 선언한 전역 변수는 선언적 환경 레코드에 등록된다. 이런 용어가 크게 중요해보이진 않는다. 중요한 것은 똑같은 전역 변수라도 저장되는 공간은 엄밀히 분리되어 있다는 것만 기억하자.
4️⃣ fn1를 호출하면 Call Stack에서 (anonymous)위에 fn1 실행 컨텍스트가 생성되어 쌓인다. 자바스크립트는 순차적으로 실행하던 전역 코드의 실행을 일시 중단하고 fn1 함수 내부로 진입한다. fn1 함수 내부의 코드를 실행하기 앞서 평가 과정을 거친다.
5️⃣ 이름을 붙이지 않고 변수를 선언하면 무조건 global 스코프에 등록된다.(당연히 오류 가능성을 높이기 때문에 권장되지 않는다.) 반면에 var 키워드가 함수 스코프안에서 선언될 때는 해당 함수 스코프에 등록된다.
6️⃣ fn2을 호출하면 Call Stack에는 (anonymous)위에 fn1 실행 컨텍스트위에 fn2 실행 컨텍스트가 쌓인다. fn2도 마찬가지로 평가, 실행 단계로 진행된다.
7️⃣ 유의할 것은 fn2이 선언된 위치다. fn2이 fn1 내부에서 호출되었기 때문에 얼핏 fn1의 스코프와 연결되어있을 것 같지만 그렇지 않다. 자바스크립트는 함수가 선언된 곳 중심으로 스코프를 평가하기 때문에 fn2의 스코프 체인은 global-script-fn2의 형태를 띈다. 때문에 fn2에선 fn1 스코프에 접근할 수 없다.
8️⃣ fn2 종료, Call Stack에서 fn2 실행 컨텍스트 제거
9️⃣ fn2 종료, Call Stack에서 fn1 실행 컨텍스트 제거
[유튜브] 생활코딩_JavaScript - Execute context
📖 모던자바스크립트 딥다이브 23장 실행 컨텍스트(259p)