실행 컨텍스트를 이해하기 전에 이 녀석이 도대체 무엇인지 알아보자.
코드를 실행하는데 필요한 환경을 제공하는 객체
환경 👉 코드 실행에 영향을 주는 조건이나 상태
그닥 와닿지는 않는다 😶
변수 객체
scope chain
this
코드가 실행되면 바로 생성되며, 컨텍스트 중 제일 먼저 호출 스택(call stack) 에 저장되는 컨텍스트
특정 함수가 실행되지 않는 한 전역 컨텍스트에서 실행됨
eval 함수는 자신만의 실행 컨텍스트를 가진다.
다만, eval 함수는 사용을 자제해야 함 ( 보안에 취약 )
이번 포스팅에선 eval 함수는 제쳐두고 전역 컨텍스트와 함수 컨텍스트만 다룰 것이다.
실행 컨텍스트가 자꾸 호출 스택(call stack) 에 저장된다고 되어있는데 호출 스택은 뭘까❓
직관적으로 보면 stack 형태로 실행 컨텍스트들을 차례대로 쌓는 바구니 같다.
여러 함수들을 호출하는 스크립트에서 해당 위치를 추적하는 인터프리터를 위한 메커니즘
위의 그림과 같이, 자바스크립트 코드가 실행되며 생성되는 실행 컨텍스트들을 스택 형태로 저장하는 자료구조라고 볼 수 있겠다.
이제 본격적으로 실행 컨텍스트를 이해해보자.
let momstouch = true;
var mybag = false;
function myhouse() {
let momstouch = false;
function myroom() {
let pet = 'cat';
console.log(pet); // cat
console.log(momstouch); // false
console.log(mybag); // false
}
myroom();
}
myhouse();
전역 컨텍스트는 코드가 실행되면 바로 호출 스택에 추가된다.
함수 컨텍스트는 함수가 호출되면 호출 스택에 추가된다.
3-0. 예제 코드를 토대로 호출 스택에 저장되는 실행 컨텍스트들을 나타내보자.
전역 컨텍스트가 제일 먼저 호출 스택에 쌓이고,
호출된 함수 순서로 함수 컨텍스트가 호출 스택에 쌓이는 것을 알 수 있다.
그렇다면 실행 컨텍스트 내부에서는 어떤 일이 일어날까❓
- 실행 컨텍스트 생성
- 선언문만 실행해서 Environment Record에 기록 (실행 컨텍스트에 저장)
- 선언문 외 나머지 코드 순차적 실행
- 실행 컨텍스트에 저장된 정보 참조 또는 업데이트
이러한 단계들을 거쳐서 실행 컨텍스트를 구성한다.
3-0. 예제 코드를 기반으로 적어보겠다.
👉 먼저, 전역 컨텍스트가 생성되고, 생성된 컨텍스트는 생성 단계를 거친다.
생성 단계를 거치면서 let
으로 선언한 변수인 momstouch
는 선언만 되고 초기화는 이루어지지 않은 것을 확인할 수 있고, var
로 선언된 mybag
은 선언과 동시에 undefined로 초기화가 이루어진다.
myhouse( )도 함수 선언문으로 선언되었기 때문에 호이스팅이 일어나서 그대로 생성 단계에서 실행 컨텍스트에 기록된다.
👉 전역 컨텍스트가 실행 단계로 들어가며 나머지 로직을 실행한다. 이 과정에서 함수 호출, 식별자 할당 등이 이루어진다. 즉, 초기화 되지 않아 아무 값이 없던 momstouch
가 true 값으로 할당 된다.
전역 컨텍스트가 생성된 이후 제일 먼저 호출되는 함수는 myhouse()
이다.
따라서 호출 스택에는 전역 컨텍스트 다음으로 myhouse( ) 함수 컨텍스트가 쌓일 것이다.
그리고 쌓인 함수 컨텍스트는 생성 단계와 실행 단계를 거쳐 아래와 같이 컨텍스트를 구성할 것이다.
myhouse 함수 내부에서 myroom 함수를 선언하고 호출한다.
따라서 myroom 함수 컨텍스트가 호출 스택에 쌓이게 된다.
쌓인 컨텍스트는 생성 단계와 실행 단계를 거쳐 아래와 같이 호출 스택이 완성된다.
console.log( )
또한 함수 호출이기 때문에 호출 스택에 함수 컨텍스트로 쌓여야 하지만 편의상console.log( )
의 호출 스택 push는 생략하겠다.
이렇게 해서 실행 컨텍스트가 어떻게 만들어지고, 호출 스택은 어떻게 쌓이게 되는지 이해할 수 있게 되었다.
실행 컨텍스트는 생성 단계와 실행 단계를 거치면서
변수/함수의 식별자, 선언, 초기화, 할당 등을 하여 저장한다.
저장할 때, 어디에다가 저장하는 것일까❓
실행 컨텍스트는 특수한 내부 객체를 가지고 있는데, 그 중에서
위의 두 가지에 대해 기록한다.
실행 컨텍스트에 저장되었던 변수/함수의 정보들은 내부적으로 모두
environment record(환경 레코드) 에 저장되었다.
변수나 함수는 특수한 내부 객체인 환경 레코드의 프로퍼티이다.
따라서 변수의 값을 변경하는 것은 환경 레코드를 참조하여 프로퍼티를 변경하는 것과 같다.
그럼 outer
는 뭘까❓
outer는 실행 컨텍스트 자신의 외부에 있는 실행 컨텍스트에 접근하기 위한 사다리라고 생각하자.
👉 그림으로 표현해보면, 아래와 같이 표현할 수 있겠다.
👉 3-0의 예제 코드를 이용해서 outer의 역할을 알아보자.
let momstouch = true;
var mybag = false;
function myhouse() {
let momstouch = false;
function myroom() {
let pet = 'cat';
console.log(pet); // cat
console.log(momstouch); // false
console.log(mybag); // false
}
myroom();
}
myhouse();
✔ myroom 함수가 실행되면서 여러 console.log( )
가 실행되는데,
차례대로 살펴보면..
pet의 값을 가져오기 위해 myroom 함수 컨텍스트의 환경 레코드를 참조한다.
myroom 함수 컨텍스트의 환경 레코드에서 pet의 정보를 찾았기 때문에 console.log( )를 문제없이 호출한다.
momstouch의 값을 가져오기 위해 myroom 함수 컨텍스트의 환경 레코드를 참조한다.
환경 레코드에서 momstouch를 찾지 못한다.
outer를 이용해 외부 렉시컬 환경인 myhouse 함수 컨텍스트로 이동 후 환경 레코드를 탐색한다.
momstouch의 값을 찾으며 console.log( )를 문제 없이 이행한다.
( false 값 출력)
mybag의 값을 가져오기 위해 myroom 함수 컨텍스트의 환경 레코드를 참조한다.
환경 레코드에서 mybag을 찾지 못한다.
outer를 이용해 myhouse 함수 컨텍스트로 이동 후 환경 레코드를 탐색한다.
환경 레코드에서 mybag을 찾지 못해 outer를 이용하여 전역 컨텍스트로 이동한다.
환경레코드를 탐색하여 mybag을 찾고 console.log( )를 문제 없이 이행한다.
( false 값 출력 )
outer를 이해하는데 굉장히 도움이 많이 되었다.
식별자를 결정할 때 활용하는 스코프들의 연결리스트
자바스크립트 엔진이 해당 스코프에서 식별자를 찾는데 발견하지 못했을 때 outer
를 이용해서 외부 렉시컬 환경 즉, 외부 스코프로 이동하여 식별자를 찾는다는 것을 알 수 있었다.
이렇게 식별자를 찾기 위해 외부 스코프로 이동하며 탐색하는 현상을
스코프 체인 이라고 한다.✔
동일한 식별자로 인해 상위 스코프에서 선언된 식별자의 값이 가려지는 현상
또 한번 같은 코드로 예시를 들어보면,
let momstouch = true;
var mybag = false;
function myhouse() {
let momstouch = false;
function myroom() {
let pet = 'cat';
console.log(pet); // cat
console.log(momstouch); // false
console.log(mybag); // false
}
myroom();
}
myhouse();
momstouch
라는 변수는 전역 컨텍스트에도 존재하고 myhouse 함수 컨텍스트에도 존재한다. myroom 함수에서 console.log(momstouch)를 실행 했을 때,
false를 출력하게 되는데 이유는
myroom 함수에서 outer를 이용해 myhouse에 도착하여 환경 레코드를 참조했을 때 momstouch라는 변수의 값을 찾았다. 이미 찾았기 때문에 굳이 전역 컨텍스트로 가서 찾으려 하지 않는다는 것이다.
이러한 현상을 변수 쉐도잉 이라고 한다.