자바스크립트 실행 컨텍스트 (Execution Context)
✏️

자바스크립트 실행 컨텍스트 (Execution Context)

Description
자바스크립트의 기본 동작중 가장 핵심이 되는 실행 컨텍스트
Tags
Published
Published June 1, 2020

실행 컨텍스트란?

한줄요약

  • 자바스크립트 엔진이 자바스크립트 코드를 읽고 실행할때 필요한 정보들의 모음(환경)
  • 자바스크립트 엔진이 코드를 실행하기 위해 런타임에 만들고 사용하는 오브젝트 셋
 
실행 컨텍스트는 자바스크립트의 핵심 중 하나이다. 자바스크립트 세계에서 종종 등장하는 호이스팅, 클로져, 스코프체인 같은 개념은 실행 컨텍스트를 제대로 이해하면 자연스럽게 따라오며 이해되는 개념들이다. ECMA 스펙에서 실행 컨텍스트는 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념이라고 정의한다. 쉽게 말하면 자바스크립트 엔진이 코드를 읽고 실행할 때 필요한 정보들의 모음(환경)인 일종의 객체이다.
 

 

거시적으로 이해하기

자바스크립트 코드가 있고 자바스크립트 엔진은 이 코드를 실행시키려고 한다. 엔진은 어떻게 이 코드를 이해하고 실행시킬 수 있을까? 코드를 실행시키는 자바스크립트 엔진의 입장에서 생각해보자.
우선 코드를 실행시키기 위해 준비하는 동작(실행 컨텍스트 생성)과 실제로 코드를 실행시키는 동작이 있다. 일단 코드를 쓰윽 스키밍, 스캐닝하며 어떤 파라미터와 변수들이 있는지, 어떤 함수들이 있는지 (실제 아직 그것이 어떤 값을 갖게 되는지, 어떤 내용의 함수인지는 모르지만), 어떤 스코프를 갖게되는지, this를 어떻게 바인딩할 것인지 등등의 정리를 한다.
정리가 끝나면 execution stack (흔히, 콜 스택이라 부르는)에 정리한 내용을 push 한 후 본격적으로 정리한 일련의 정보 객체를 바탕으로 코드를 실행한다. 변수를 만나면 아까 execution stack에 정리한 내용의 변수 관련 오브젝트에 변수 값을 맵핑한다.
함수 실행문을 만나면 위의 동작들을 반복하고 실행이 종료되면 execution stack에 쌓여있는 execution context는 스택에서 소멸되고 다음 execution context의 실행을 하며 모든 execution context의 실행이 끝날 때까지 반복한다.
 

 

실행 컨텍스트의 세가지 타입과 Execution Stack

자바스크립트는 싱글 쓰레드 언어이다. 자바스크립트 엔진은 하나의 실행 스택 가장 최상위 함수를 실행시킨다. 프로그램(코드 파일)이 실행되면 맨 처음 기준이 되는 Global EC(Global Execution Context)가 생성되고 실행 스택에 푸시되고 실행된다. 실행 도중 함수 호출 문을 만나면 해당 함수의 Functional EC(Functional Execution Context)가 생성되고 스택에 푸시되고 실행된다. 실행이 끝난 컨텍스트는 스택에서 제거(pop)된다. 스택이 빌 때까지 위의 작업을 반복하고 Global EC까지 소멸되면 프로그램이 종료된다.
 
notion image

실행컨텍스트의 세가지 타입

  • Global execution context
    • 자바스크립트 엔진이 스크립트 파일을 실행하기에 앞서 가장먼저 글로벌 실행 컨텍스트가 생성된다.
    • 글로벌 컨텍스트는 프로그램에 오직 하나만 존재한다.
    • 즉, 프로그램의 기준이 되는 대장 컨텍스트 인셈이다.
  • Functional execution context
    • 글로벌 컨텍스트가 코드를 실행하다가 함수 실행문을 만나면 함수 실행 컨텍스트가 생성되고 스택이 형성된다.
  • Eval function execution context
    • eval() 함수를 사용하여 문자열 형태로 코드를 실행할 수 있는데 eval도 실행 컨텍스트를 생성하나 자바스크립트 환경에서 잘 쓰이지 않음.
    •  

Execution Stack (Call Stack)

Execution stack은 런타임에 실행 컨텍스를 저장하기 위해 자바스크립트 엔진에 의해 생성되고 관리되는 Last-In-First-Out(LIFO) 스택의 자료구조이다.
반복해서 얘기한 것처럼 실행 컨텍스트를 실행하던 중 함수 실행문을 만나면 해당 컨텍스트(caller)는 일시 정지되고 새로운 컨텍스트(callee)가 생성, 실행되고 컨트롤이 callee에게 넘어간다. callee의 실행이 완전히 끝나면 다시 컨트롤은 caller에게 리턴되고 남은 실행을 이어나간다.
 

 

실행 컨텍스트의 구조

 
ES5이전 실행 컨텍스트는 변수 객체(Variable Object), 활성 객체(Activation Oject)의 개념으로 코드 스코프 처리를 하였다. 하지만 ES5버전부터는 Lexical Envionment의 개념으로 재정의 되었다.
실행컨텍스트를 오브젝트 형태의 수도 코드로 간단하게 나타내면 아래와 같다.
 
notion image
 
 
LexicalEnvironment, VariableEnvironment 두 컴포넌트는 기본적으로 Lexical Environment에 대한 참조이다. 처음에는 같은 Lexical Environment를 참조하나 코드 상황에따라 참조가 바뀌기도 한다. (참고)
 
함수의 호출
이로서 우리가 알수있는것은 Global Execuion Context를 제외한 대부분의 Execution Context는 함수의 호출에 의해서 생성 되고 사용된다는 것이다. 함수 호출을 간단하게 정리해보면 다음과 같다.
  1. 함수를 호출하면 이에 맞춰서 함수를 실행할 수 있는 환경을 만들고 초기화한다.
  1. this를 바인딩한다. (this가 어떤 객체를 참조해야 할지 결정한다.)
  1. 실제 함수 코드를 수행하고 그 결과를 result에 저장한다.
  1. 이 함수를 호출했던 곳(환경)으로 돌아가며,result를 반환한다.
 
 

Lexical Environment (lexicalEnvironment, variableEnviroment)

Lexical Environment는 자바스크립트 코드에서 변수나 함수 등의 식별자를 정의하는데 사용하는 객체로 생각하면 쉽다.
Lexical Environment는 3가지의 컴포넌트로 구성된다.
  1. Environment Record
  1. Outer (reference to the outer environment)
  1. This binding (ES6부터 This바인딩은 Lexical Environment에서 담당한다)
 
Lexical Environment는 스펙에만 존재하는 '이론상의'객체이다. 따라서 직접 Lexical Environment를 조회하거나 조작하는것은 불가능하다.
 
Environment Records
Environment Record는 식별자와 참조 혹은 값이 기록되는 공간이다.즉, 변수와 함수 등이 기록되는 곳이다. 자세히 들어가면 상속의 관계를 갖는 아래와 같은 타입들이 있지만. 심플하게는 Declarative environment record와 Object environment record로 생각하고 이해해도 충분할것 같다. (ES6기준)
 
Outer
outer는 외부 Lexical Environment를 참조하는 포인터로, 중첩된 자바스크립트 코드에서 스코프 탐색을 하기 위해 사용한다. 지금은 공식적으로 잘 쓰지 않지만 스코프체인 혹은 스코프 체이닝 (ES5부터는 Lexcial nesting structure 등으로 표현) 의 개념이라고 생각하면된다.
간단하게 히스토리를 덧붙이자면 ES3 에서는 스코프 탐색을 List 형식의 참조를 따라 찾아가는 방식이었고 ES5부터는 Outer의 참조를 활용하는 구현으로 바뀌었다.
 
notion image
 
BarEnvironment에서는 globalVarfooVar 를 찾을 수 없다 따라서 outer의 참조를 이용하여 아래와 같이 변수를 찾는다. foobar같은경우 recursive하게 outer참조를 타고 올라가 outer가 null인 GlobalEnvironment에서도 찾을수 없는 식별자면 Reference Error가 발생한다.
notion image
 
This Binding
이름 그대로 this의 값이 결정되고 설정된다.
Global execution context에서 this는 global obejct에 바인딩된다 (예를들어 브라우져라면 Window Object)
Function execution context에서는 어떻게 호출되었는가에 따라 this의 값이 결정된다. 관련 해서는 따로 포스팅(링크)한 내용을 참고하자. (정확히는 기본적으로는 global object지만 호출 방식에따라 다른 레퍼런스로 바인딩 된다.)
 

 

실행 컨텍스트의 생성과 실행

주제는 실행컨텍스트였는데 LexicalEnvironment의 설명만 길었다. 그럴수밖에 없는게 ES6를 기준으로 했을때 실질적으로 자바스크립트 개발을 하는데 있어서 이해해야되는 동작 자체는 Lexical Environment의 지분이 크기 때문인거 같다.
 
아래의 수도코드는 실행 컨텍스트의 전체적인 흐름을 이해하기 위한것으로 실제와 다르고 많은것들이 생략되어 있습니다.
 
실행 컨텍스트는 생성 / 실행 의 두가지 단계의 동작을 한다.
생성 단계 (Creation Phase) 에서는 실행가능한 코드를 스캐닝 하면서 LexicalEnvironment와 VariableEnvironment를 생성한다.
프로그램(코드)가 실행되면 제일먼저 Global Execution Context가 생성된다.
 
notion image
이때 let과 const는 LexicalEnvironment의 Environment record에 식별자가 생성(변수 선언)되며 초기화가 되지 않지만 var의 경우 선언과 초기화(undefined)가 일어난다. (함수 선언식의 경우 그냥 함수 자체의 레퍼런스가 선언되고 할당된다고 생각하는것이 이해하기 수월한것 같다)
(this 바인딩이나 함수의 세부적인 동작은 생략합니다.)
 
 
notion image
Lexical Environment (LexicalEnviroment, VariableEnvironment)의 생성이 끝나면 실행단계(Execution Phase)가 시작되며 엔진은 코드를 한줄씩 읽어 내려가면서 선언 되어있는 식별자에 값을 할당 한다.
이때 만약 초기화 되지 않은 변수에 접근하면 Reference Error를 발생시키게된다. 이것이 자바스크립트에서 흔히 말하는 호이스팅이다. var의경우 실행 컨텍스트 실행단계에서 초기화까지 하여 Reference Error가 발생하지 않지만 let과 const의 경우 선언만되고 초기화가 되지 않은 상태이기 때문에 (이를 Temporal Dead Zone - TDZ라한다) 에러가 발생한다.
 
notion image
코드를 실행시키다 함수 호출문을 만나게 되면 해당 함수의 Execution Context가 생성된다.
 
 
notion image
함수 실행 컨텍스트도 마찬가지로 Creation phase와 Execution phase를 거친다.
위의 예제에는 해당 경우가 없지만 만약 실행단계에서 찾는 변수가 존재하지 않으면 outer의 레퍼런스를 통해 상위 Lexical Environment에서 변수를 찾고 만약 outer가 null인 글로벌 오브젝트에서까지 찾지못하면 에러를 발생한다.
 
 
notion image
notion image
함수 실행 컨텍스트도 마찬가지로 생성, 실행 단계를 거치고 실행이 완료되면 (반환할 result가 있으면 반환) Execution Stack에서 제거되며 다시 상위 실행 컨텍스트 (caller) 로 복귀하여 위의 동작들을 반복한다.
 

 

요약

  • 엔진이 코드를 실행 시킬때 EC 라는 실행 가능한 환경을 만들고 그걸 기준으로 코드를 실행함
    • 크게 전역에서 생성되는 전역EC와 함수실행 코드를 만나면 생성되는 함수EC가 있음
  • EC는 생성과 실행의 단계로 나누어짐
  • 생성단계일때 Lexical Environment를 생성함 (lexicalEnvironment / variableEnvironment)
    • 이때 LE에는 스코프의 변수와 함수 선언
    • let과 const는 선언만, var는 선언, 초기화 까지
    • 함수선언문은 선언,초기화,할당 까지
  • 실행단계일때 스크립트를 읽으며 생성한 LE에 할당 및 코드 실행
    • 함수 호출문을 만나면 현재 EC는 일시정지, 함수 EC를 생성 Execution Stack에 푸시후 실행
    • 함수EC의 실행 및 result 일시정지한 상위 EC로 return
  • Execution Stack이 빌때까지 위의 동작을 반복
 

마치며

공부를 하고 정리를 하다보니 ECMA 버전에 따라서 달라지는 스펙들도 많았고 세부적으로 들어가면 워낙 각각의 역할이 딥하고 엉켜있고 디펜던시가 있어서 모든것을 한꺼번에 이해하고 정리하기도 힘들고. 그렇다고 하나씩 깊게 뜯어서 정리하는것도 뜬구름 잡는것 같은 느낌이 아닐까 싶다. (this바인딩이나 클로져 같은 개념도 한꺼번에 정리하고 싶었지만...) 따라서 개인적으로는 전체적인 흐름을 이해하고 필요에 따라 하나씩 깊게 공부하는게 올바른 방향인것 같다.
 

좋은글

Lexical Environment