https://codesmoothie.tistory.com/27
지난번 포스팅에서 자바스크립트의 엔진과 런타임에 대해서 알아보았다. 이번 포스팅에서는 자바스크립트의 실행 컨텍스트와 콜 스택이 어떻게 동작하는지 살펴보자.
실행(Execution) 컨텍스트(Contexts)란 무엇인가?
이제 막 컴파일링 끝난 기계 코드가 있다고 가정해보자. (컴파일에 대한 개념은 지난 포스팅 참조)
그 직후 코드는 실행(Execution)할 수 있는 준비를 마치게 되며, 상위 레벨 코드(top-level code)를 위한 Global execution context를 생성한다. 여기서 상위 레벨 코드란 어떠한 함수에도 들어가지 않는 코드를 의미한다. 다시 말해, 자바스크립트 엔진이 코드를 실행할 때 처음에는 함수 밖에 있는 코드만을 실행한다는 것이다. 아래 이미지의 코드를 살펴보자. const name = 'Jonas';라는 부분이 상위 레벨 코드이며, Global Execution Context에서 실행될 것이다. 다음 코드를 살펴보면 두개의 함수가 존재한다. 하나는 표현형(expression), 다른 하나는 선언형(declaration) 함수이다. 함수안에 있는 코드는 오직 함수가 호출되었을 때만 실행된다.
실행 컨텍스트란 자바스크립트를 실행시키기 위한 환경이다. 코드를 실행시키기 위한 모든 필요한 정보를 저장시키는 박스와 비슷하다. 이러한 정보에는 지역(local) 변수(variables)나 함수에 들어가는 인수(arguments) 같은 것들이 있다. 자바스크립트 코드는 항상 실행 컨텍스트 안에서 실행된다. 피자를 테이크아웃 한다고 상상해보자. 일반적으로 해당 피자는 박스안에 담겨 있으며, 포크나 영수증, 핫소스 등 다른 것들도 포함되어 있다. 여기서 피자를 자바스크립트 코드라고 할 수 있으며 피자박스는 실행 컨텍스트에 비유할 수 있다.
어떤 자바스크립트 프로젝이든 오직 1개의 Global Execution Context만이 존재한다. Global Excution Context는 기본 Context이며 상위 레벨 코드를 실행 시킨다.
Exactly one global execution context (EC): Default context, created for code that is not inside any function (top-level).
Global Execution Context로 인해 상위 레벨 코드가 실행되고 그 다음 함수가 실행된다. 모든 함수가 호출될때마다 해당 함수를 실행시키기 위한 필요한 모든 정보를 포함하는 새로운 실행 컨텍스트가 생성된다. 이 과정은 객체에 있는 메소드도 동일하게 적용된다. 이러한 과정들이 모두 합쳐져 콜 스택을 구성하게 된다.
이제 모든 함수들이 실행되었다고 가정해보자. 자바스크립트 엔진은 콜백 함수가 도착하기만을 기다리게 된다. 예를 들어, 콜백 함수가 클릭 이벤트와 연결되어 있다고 해보자. 이 경우 이벤트 루프가 해당 콜백 함수를 전달하게 된다.
실행 컨텍스트의 구성요소
실행 컨텍스트에는 아래와 같이 3가지 구성요소가 존재한다. 물론 Top Level도 포함된다.
1. 변수(Variable) 환경(Environment)
- let, const and var declarations
- Functions
- arguments object
변수 환경에는 모든 변수와 선언형(delcarations) 함수 그리고 특별한 형식의 arguments object가 저장된다. 이 object에는 현재 실행 컨텍스트에 속해있는 함수로 전달되는 모든 arguments가 포함된다. 이는 각각의 함수마다 고유한 실행 컨텍스트를 가지고 있기 때문이다. 기본적으로 함수 안에서 선언된 모든 변수들은(variables) 결국 변수 환경에서만 존재한다. 그러나 함수는 함수 밖에서도 변수에 접근할 수 있다. 이러한 동작을 가능하게 하는 것은 스코프(Scope) 체인(Chain)이라고 한다.
2. 스코프(Scope) 체인(Chain)
스코프 체인은 현재 함수 밖에 위치한변수들의 참조로 구성된다. 스코프 체인은 각각의 실행 컨텍스트에 저장된다.
3. this 키워드
이러한 구성요소들은 코드가 실행되기 직전인 생성 단계(Creation phase)에서 생성된다. 여기서 중요한 사실은 실행 컨텍스트가 화살표 함수에 속해있다면 arguments와 this 키워드는 가져오지 않는다는 것이다. 즉, 화살표 함수에는 기본적으로 arguments object와 this 키워드가 존재하지 않는 것이다. 그 대신에 arguments object와 this 키워드를 가장 가까운 상위 일반 함수로 부터 가져온다.
예시를 통한 실행 컨텍스트의 이해
먼저, Global EC가 먼저 생성된다. 그리고 각 함수마다 EC가 생성된다. 하나는 first 함수이고 다른 하나는 second 함수이다.
Global EC에는 const로 선언된 name 변수와 first, second 함수 선언 그리고 x 변수 선언이 있다. 함수의 경우 문자 그대로 특정 함수의 코드를 모두 포함하고 있다. x의 경우 unknown으로 표현된다. 이 값은 아직 실행하지 않은 first 함수의 결과이기 때문이다. 기술적으로 이러한 값들은 생성(평가) 단계에서는 알 수 없고 오직 실행 단계에서만 알 수 있다.
first 함수에서는 a 변수의 값이 1이고, b 변수는 함수의 호출이 필요하다는 것을 알 수 있다.
최종적으로 second 함수의 변수(variable) 환경(environment)에서는 c 변수의 값이 2이고, 화살표 함수가 아니라 일반 함수이기 때문에 arguments object가 존재하는 것을 알 수 있다. 해당 객체는 배열이며 함수를 호출할 때 넣어준 7, 9와 값이 동일하다.
상기의 예시는 코드가 짧아서 간단해보이지만 만약 100개 이상이라면 어떻게 될까? 이 경우 자바스크립트 엔진은 어떻게 함수의 실행 순서를 관리하고 현재 어디서 코드가 실행되고 있는지 파악할까? 이러한 질문에 답변하기 위해 마지막으로 Call Stack를 자세히 이해해야 한다.
콜(Call) 스택(Stack)
이전 포스트에서 자바스크립트 엔진을 구성하는 콜 스택은 메모리 힙과 같이 존재한다고 언급했다. 콜 스택을 자세히 알아보기 위해 메모리 힙의 경우 생략하여 설명한다.
콜 스택이란 프로그램이 어디쯤 실행되고 있는지 기록하기 위한 실행 컨텍스트가 겹겹이 쌓이는 장소이다. 콜 스택의 맨 위에 있는 실행 컨텍스트가 현재 실행되고 있는 것이다.
실행이 끝나면 콜 스택에서 제거되고 이전의 실행 컨텍스트로 돌아가게 된다. 이는 친구들과 함께 피자를 산 경우로 비유할 수 있다. 어떤 피자가 친구 것인지 알아내기 위해서 피자 상자를 준비해서 그 상자를 겹겹이 쌓는다고 상상해보자. 추상적이기 때문에 상기에서 작성했던 코드 예시를 통해 어떻게 콜 스택이 동작하는지 알아보자.
일단 코드가 컴파일되면 상위 레벨 코드 (top-level code)가 실행되기 시작한다. 그리고 이를 위해 Global 실행 컨텍스트가 생성된다. 함수 외의 모든 코드가 실행되는 곳이다. 그리고 Global EC는 콜 스택에 놓이게 되며 스택의 가장 상단에 위치해 있기 때문에 현재 코드가 실행되는 곳이다.
상위 레벨 코드는 const name = 'Jonas'; 변수 선언 이후 first 함수 선언, second 함수 선언 순으로 실행된다. 화려하지는 않지만 일반적인 상위 레벨 코드는 이러한 방식으로 실행된다. 하지만 마지막 x 변수를 선언할 때 흥미로운 일이 발생한다. 해당 값은 first 함수를 호출 했을 때 값이 반환된다. 해당 함수를 호출하면 어떤 일이 발생할까? first 함수의 고유 실행 컨텍스트를 가져와서 함수 안의 코드를 실행할 수 있게 된다. 실행 컨텍스트는 아래의 이미지와 같이 콜 스택 최상단에 위치하며 현재 실행 중인 실행 컨텍스트가 된다.
first 함수에는 a라는 간단한 변수 선언 코드가 존재한다. 이 변수는 Global이 현재 실행 컨텍스트인 first 함수의 변수 환경에 정의된다. 다음줄에는 second라는 함수 호출이 존재한다. 이후 second 함수를 위한 새로운 실행 컨텍스트를 생성하고 다시 한 번 콜 스택으로 푸쉬된다.
여기서 중요한 것은 first 함수의 실행이 잠깐 중단된다는 것이다. 즉, second 함수를 실행하는 동안 다른 함수는 실행되지 않는다는 것이다. first 함수는 second 함수가 호출된 지점에서 멈추었고, second 함수가 return(반환) 되어야지만 실행을 이어간다. 자바스크립트는 싱글 스레드 언어이기 때문이다. 다음줄에 return문이 나온다. (참고로 return은 함수의 실행을 끝낸다는 뜻) return 문으로 인해 second 함수의 실행 컨텍스트가 콜 스택에서 제거되어 메모리에서 사라진다.
이제 first 함수의 실행 컨텍스트가 다시 활성화 된다. 코드 실행은 전에 함수를 호출했던 곳에서 다시 시작한다. (콜 스택이 코드의 실행이 분실되지 않고 순서를 관리하는 지도와 같다는 것을 알 수 있다) second 함수의 값을 반환되었고 해당 값을 기반으로 a = a + b를 계산하였다. 최종적으로 a값을 return하면서 first 함수도 콜 스택에서 제거되면서 Global EC가 현재 실행 컨텍스트가 된다.
마찬가지로 first 함수가 처음 호출된 코드에서 실행이 계속된다. return된 값이 드디어 x에 할당되고 실행이 최종적으로 끝난다. 이제 프로그램은 브라우저를 닫을 때까지 영원히 이 상태로 머무르게 된다. 브라우저를 종료하게 되면 Global EC가 제거된다.
'Javascript' 카테고리의 다른 글
[JavaScript] null 병합 연산자 ?? (1) | 2024.02.13 |
---|---|
[Javascript] 자바스크립트 엔진과 런타임 (1) | 2024.02.06 |
[Javascript] 자바스크립트란 어떤 언어인가? (0) | 2024.02.05 |
[JavaScript] 호이스팅(hoisting)이란? (1) | 2024.01.30 |
코드 에디터를 위한 iframe 디자인 패턴 (0) | 2024.01.10 |