이전에 작성한 포스팅인 [JAVASCRIPT] var로 알아보는 변수 선언과 할당 에서 가볍게 다루었던 실행 컨텍스트에 대한 내용을 정리한다.
실행 컨텍스트(execution context)는 자바스크립트의 동작 원리를 담고 있는 핵심 개념이다.
실행 컨텍스트를 이해하면 스코프, 호이스팅의 발생 이유, 클로저의 동작 방식, 태스크 큐와 함께 동작하는 이벤트 핸들러, 비동기 처리 동작 방식 등을 이해할 수 있게된다.
실행 컨텍스트
실행 컨텍스트란 자바스크립트 엔진이 소스코드를 평가하고 실행하기 위해 필요한 환경을 제공하고 코드의 실행결과를 실제로 관리하는 영역이다.
조금 더 정리하자면 '코드를 실행하는 데에 필요한 배경이 되는 조건 / 환경',
더 쉽게 말하자면 '코드의 실행 환경'이다.
어떠한 동일한 조건 / 환경을 지니는 코드 뭉치가 있고 이를 실행할 때, 이 조건 / 환경 정보를을 모아 컨텍스트를 구성한다.
그리고 이를 콜스택(call stack)에 쌓아둔다.
콜스택의 가장 위에 쌓인 컨텍스트와 관련있는 코드를 실행하는 방식으로 전체 코드의 환경과 순서를 보장한다.
자바스크립트에서는 동일한 환경을 가질 수 있는 소스코드에 4가지 타입이 존재한다.
이 소스코드들은 각기 다른 과정으로 실행 컨텍스트를 생성하고 내용물을 관리한다.
- Global Code (전역공간) ---> Global Execute Context
- Function Code (함수) ---> Function Execute Context
- Eval Code ---> Eval Execute Context
- Module Code ---> Module Execute Context
* eval은 여러 가지 문제를 야기하는 위험한 명령어로 거의 다루지 않아 보통 논외로 한다.
전역공간은 자바스크립트 코드가 실행되는 순간에 바로 전역 콘텍스트를 생성, 전체 코드가 끝날 때에 비로소 전역 컨텍스트가 종료되니 사실상 하나의 거대한 함수 공간이라고 봐도 무방하다. 모듈 역시 impor되는 순간에 컨텍스트를 생성, 모듈 코드가 끝날 때에 종료되니 역시 하나의 함수 공간이라고 간주할 수 있다.
결국 자바스크립트에서 동일한 환경을 가질 수 있는 것은 함수 뿐.
함수에 의해서만 컨텍스트를 구분할 수 있다라고 말할 수 있다.
정리하자면 실행 컨텍스트는 '함수를 실행할 때 필요한 환경 정보를 담은 객체'이다.
Call Stack
콜스택(call stack. 호출스택)이란 현재 어떤 함수가 동작 중인지, 다음에 어떤 함수가 호출될 예정인지 등을 제어하는 자료구조이다.
FILO(First in, Last out)를 준수하며 각 실행 컨텍스트를 콜스택 위로 쌓아 올리고, 가장 위에 쌓여있는 컨텍스트와 관련된 코드를 실행한다. 이를 통해 전체 코드의 환경과 순서를 보장한다.
다음 예제를 통해 컨텍스트가 콜스택에 쌓이는 과정을 알아보자.
var a = 1;
function outer() {
console.log(a); // ----------- 실행순서 1
function inner() {
console.log(a); // --------- 2
var a = 3;
}
inner();
console.log(a); // ----------- 3
}
outer();
console.log(a); // ------------- 4
1. 제일 먼저 전역 컨텍스트가 열리고, 전역공간을 한 줄 한 줄 실행한다.
2. outer(); 를 만나 함수를 호출, outer()에 대한 실행 컨텍스트가 열리고, 내부를 한 줄 한 줄 실행한다.
3. inner(); 를 만나 함수를 호출, inner()에 대한 실행 컨텍스트가 열리고, 내부를 한 줄 한 줄 실행한다.
4. inner()와 outer(), 그리고 전역까지 순서대로 종료한다.
Q. 위 예제의 실행 결과는?
1
undefined
1
1
1, 3, 4번째에서는 해당 함수 스코프 내에 변수 a가 없기 때문에 스코프 체인을 따라 전역 변수 a를 참조해 그 값인 1을 출력한다.
2번째에서는 해당 함수 스코프 내에서 var a = 3;의 선언 부분만 호이스팅되어 undefined 값을 가진다.
여기서 언급된 스코프 체인과 호이스팅에 대해서는 아래에서 설명한다.
실행 컨텍스트의 내부 구성
어떤 실행 컨텍스트가 콜스택의 맨 위에 쌓이는 순간에, 즉 활성화될 때(running execution context)
실행 컨텍스트의 내부에는 3가지의 환경 정보가 저장된다.
- Variable Environment : 현재 환경과 관련된 식별자의 정보를 수집한다
- 선언 시점의 Lexical Environment의 스냅샷으로, 변경 사항 반영되지 않는다
- Lexical Environment : 현재 환경과 관련된 각 식별자에 담긴 데이터를 추적한다
- 처음은 Variable Environment와 동일하지만, 변경 사항이 실시간으로 반영된다 (예. 변수 재할당, 새로운 식별자 추가)
- ThisBinding : this 식별자가 바라봐야 할 대상 객체이다
정리한 것과 같이 Variable Environment와 Lexical Environment는 변경 사항의 반영 여부의 차이이므로, 아래에서는 실시간 반영의 Lexical Environment를 위주로 살펴본다.
Lexical Environment
직역하자면 어휘적/사전적 환경을 의미하며
'실행 컨텍스트를 구성하는 환경 정보들을 모아 마치 사전처럼 구성한 객체'이다.
사전처럼이라는 말이 무엇이냐, "현재 컨텍스트 내부에는 a, b와 같은 식별자들이 있고, 그 외부 정보는 D를 참조하도록 구성되어 있다"와 같은 느낌이다.
이 환경 정보들에는 내부 식별자에 대한 정보와 외부 정보가 포함되어 있다.
- environmentRecord : 현재 컨텍스트 내부의 식별자 정보
- outerEnvironmentReference : 외부 환경을 참조하는 정보
- 현재 컨텍스트와 관련이 있는 외부 컨텍스트의 식별자 정보를 참조
environmentRecord와 Hoisting
environmentRecord에는 현재 컨텍스트의 식별자 정보가 저장된다.
이는 실행 컨텍스트가 최초 실행될 때 제일 먼저 하는 일이다.
현재 컨텍스트 식별자 정보를 수집해서 environmentRecord에 담는 과정, 이를 호이스팅(hoisting)이라고 한다.
이전 포스팅에서 호이스팅에 대해 런타임에 앞서 소스코드를 실행하기 위한 준비 과정으로 선언문만 먼저 실행하는 것이라고 정의했었다. 코드의 최상단으로 끌어올려지는 실제 현상이 아닌 environmentRecord를 좀 더 쉽게 이해하기 위해서 만든 허구의 개념으로 이해하는 것이 좋다.
outerEnvironmentReference와 Scope Chain
outerEnvironmentReference에는 현재 컨텍스트와 관련이 있는 외부 컨텍스트의 식별자 정보가 저장된다.
앞서 존재했던 코드 예시에서 콜스택이 다 쌓였을 때 (현재 실행 중인 컨텍스트가 inner인 경우)
각 실행 컨텍스트 내부(Lexical Environment)의 구성을 가시화하면 아래와 같다.
inner 실행 컨텍스트의 outerEnvironmentReference는 바로 밑에 있는 outer 실행 컨텍스트를 참조한다.
outer 실행 컨텍스트의 outerEnvironmentReference는 바로 밑에 있는 전역 실행 컨텍스트를 참조한다.
그리고 이 outerEnvironmentReference이 만드는 것이 스코프 체인(scope chain) 현상이다.
스코프(scope)는 식별자의 유효 범위를 말한다. 이 유효 범위는 실행 컨텍스트에 의해서 결정된다. 실행 컨텍스트가 수집해 놓은 정보에만 접근할 수 있고, 그 변수는 실행 컨텍스트 내부에서만 존재한다. 즉, inner에서 선언한 식별자는 inner에만 사용할 수 있는 것이다.
그런데 만일, inner에서 접근해야 할 식별자가 inner 컨텍스트의 environmentRecord에 저장되어 있지 않은 식별자라면?
inner 컨텍스트의 outerEnvironmentReference를 통해 outer 컨텍스트에 저장된 식별자인지 찾아볼 수 있다. 만일 outer에도 저장되지 않은 식별자라면 또 전역 컨텍스트에서 찾아볼 수 있다.
이런 식으로 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해 나가는 것, 이를 스코프 체인이라고 한다.
가장 가까운 컨텍스트부터 차례로 접근할 수 있으며 다른 순서로 접근하는 것은 불가능하다. 이를 shadowing이라고 한다.
때문에 여러 스코프에서 동일한 식별자를 선언한 경우에는 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근이 가능하다.
위의 내용을 토대로 처음에 등장했던 예제의 실행 순서와 결과를 정리하면 다음과 같이 작성할 수 있다.
var a = 1;
function outer() {
console.log(a);
function inner() {
console.log(a);
var a = 3;
}
inner();
console.log(a);
}
outer();
console.log(a);
// 출력 결과
// 1
// undefined
// 1
// 1
참고
[책/강의] 코어 자바스크립트 - 02_실행 컨텍스트
[책] 모던 자바스크립트 Deep Dive - 23_실행 컨텍스트
(JavaScript)실행 컨텍스트 - 클로저와 호이스팅 - ZeroCho Blog
'JAVASCRIPT' 카테고리의 다른 글
[JAVASCRIPT] Asynchronous : 비동기 (비동기 동작) (0) | 2024.10.08 |
---|---|
[JAVASCRIPT][PCCP 대비] 프로그래머스 레벨1, 2 빠르게 풀고 메모하기 (0) | 2024.05.08 |
[JAVASCRIPT] var로 알아보는 변수 선언과 할당 - 실행 컨텍스트 / 변수 호이스팅 / TDZ / 가비지 컬렉터 (1) | 2024.01.06 |
[JAVASCRIPT] Callback Function : 콜백 함수 - 타이머 함수 / 비동기 / Promise / async - await (0) | 2023.08.27 |
[JAVASCRIPT] Function : 함수 (0) | 2023.08.27 |