Execution Context

js_logo

정의

코드의 실행환경에 대한 여러가지 정보를 담고 있는 개념으로, 간단히 말하자면 자바 스크립트 엔진에 의해 만들어지고, 사용되는 코드 정보를 담은 객체의 집합이라고 할 수 있다.



  • scope, hoisting, this, function, closure 등의 동작원리를 담고 있는 자바스크립트의 핵심원리.

  • 실행 컨텍스트는 실행 가능한 코드가 실행되기 위해서 필요한 환경



실행 컨텍스트(excution context)는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체로, 자바스크립트의 동적 언어로서 성격을 가장 잘 파악할 수 있는 개념이다. 자바스크립트는 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올리고(호이스팅), 외부 환경 정보를 구성하고, this값을 설정하는 등의 동작을 수행한다. 이로 인해 다른 언어에서는 발견할 수 없는 특이한 현상들이 발생한다.

실행 컨텍스트는 자바스크립트의 가장 중요한 핵심 개념 중 하나이다. 사실 클로저를 지원하는 대부분의 언어에서 이와 유사하거나 동일한 개념이 적용되어 있다.

실행 컨텍스트란?

실행할 코드에 제공할 환경 정보들을 모아놓은 객체라고 했는데, 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 콜 스택(call stack)에 쌓아올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과ㅏ 순서를 보장한다. 여기서 ‘동일한 환경’, 즉 하나의 실행컨텍스트를 구성할 수 있는 방법으로 전역공간, eval() 함수, 함수 등이 있다. 자동으로 생성되는 전역공간과 eval()을 제외하면 우리가 흔히 실행 컨텍스트를 구성하는 방법은 함수를 실행하는 것뿐이다.

자바스크립트를 실행하는 순간, 전역 컨텍스트가 콜 스택에 담긴다. 브라우저에서 자돋ㅇ으로 실행하므로 파일이 열리는 순간 전역 컨텍스트가 활성화된다고 이해하면 된다.

콜 스택에는 전역 컨텍스트 외에 다른 덩어리가 없으므로 전역 컨텍스트와 관련된 코드들을 순차로 진행하다가 함수를 호출하면 자바스크립트 엔진은 그 함수에 대한 환경 정보를 수집해서 해당 함수 실행 컨텍스트를 생성한 후 콜 스택에 담는다. 콜 스택의 맨 위에 담은 함수가 있기에 전역 컨텍스트와 관련된 코드의 실행을 일시중단하고 함수의 실행 컨텍스트와 관련된 코드, 즉 함수 내부의 코드들을 순차로 실행한다.

이렇게 어떤 실행 컨텍스트가 활성화될 때 자바스크립트 엔진은 해당 컨텍스트에 관련된 코드들을 실행하는 데 필요한 환경 정보들을 수집해서 실행 컨텍스트 객체에 저장한다. 이 객체는 자바스크립트 엔진이 활용할 목적으로 생성할 뿐 개발자가 코드를 통해 확인할 수는 없다. 여기에 담기는 정보들은 다음과 같다.

  • VariableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부환경 정보. 선언 시점의 LexicalEnvironment의 스탭샷(snepshot)으로, 변경 사항은 반영되지 않는다.

  • LexicalEnviroment: 처음에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영됨.

  • ThisBinding: this 식별자가 바라봐야 할 대상 객체.

VariableEnvironment

VariableEnviroment에 담기는 내용은 LexicalEnviroment와 같지만 최초 실행 시의 스냅샷을 유지한다는 점이 다르다. 실행 컨텍스트를 생성할 때 VariableEnviroment에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LexicalEnviroment를 만들고, 이후에는 LexicalEnviroment를 주로 활용하게 된다.
VariableEnviroment와 LexicalEnvironment의 내부는 enviromentRecord와 outer-EnviromentReference로 구성되어 있다. 초기화 과정 중에는 사실상 완전히 동일하고 이후 코드 진행에 따라 서로 달라지게 된다.

LexicalEnvironment

현재 컨텍스트 내부에는 a,b,c와 같은 식별자들이 있고 그 외부 정보는 D를 참조하도록 구성돼 있다. 컨텍스트를 구성하는 환경 정보들을 사전에서 접하는 느낌으로 모아놓은 것.

environmentRecord와 호이스팅

enviromentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다. 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체, var로 선언된 변수들의 식별자 등이 식별자에 해당한다. 컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 순서대로 수집한다.

전역 실행 컨텍스트는 변수 객체를 생성하는 대신 자바스크립트 구동 환경이 별도로 제공하는 객체. 즉 전역 객체(global object)를 활용한다. 전역 객체에는 브라우저의 window, Node.js의 global 객체 등이 있다. 이들은 자바스크립트 내장 객체(native object)가 아닌 호스트 객체(host object)로 분류된다.

변수 정보를 수집하는 과정을 모두 마쳤더라도 아직 실행 컨텍스트가 관여할 코드들은 실행되기 전의 상태이다. 코드가 실행되기 전임에도 불구하고 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명들을 모두 알고 있게 되는 것이다. 그렇다면 엔진의 실제 동작 방식 대신에 ‘자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다.’라고 생각하더라도 코드를 해석하는 데는 문제될 것이 전혀 없을 것이다. 여기서 호이스팅(hoisting)이라는 개념이 등장한다. 호이스팅이란 ‘끌어올리다’라는 의미의 hoist.

스코프, 스코프 체인, outerEnvironmentReference

스코프(scope)란 식별자에 대한 유효범위이다. 어떤 경계 A의 외부에서 선언한 변수는 A의 외부뿐 아니라 A의 내부에서도 접근이 가능하지만, A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근할 수 있다. 이러한 스코프의 개념은 대부분의 언어에 존재한다. 자바스크립트도 예외는 아니기에, 다만 ES5까지의 자바스크립트는 특이하게도 전역공간을 제외하면 오직 함수에 의해서만 스코프가 생성된다. 어쨌든 이러한 ‘식별자의 유효범위’를 안에서부터 바깥으로 차례로 검색해나가는 것을 스코프 체인(scope chain)이라고 한다. 그리고 이를 가능케 하는 것이 바로 LexicalEnvironment의 두 번째 수집 자료인 outerEnvironmentReference 이다.

스코프 체인

outerEnviromentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조한다. 과거 시점인 ‘선언될 당시’에 주목하면, ‘선언하다’라는 행위가 실제로 일어날 수 있는 시점이란 콜 스택 상에서 어떤 실행 컨텍스트가 활성화된 상태일 때 뿐이다. 어떤 함수를 선언(정의)한느 행위 자체도 하나의 코드에 지나지 않으며, 모든 코드는 실행 컨텍스트가 활성화 상태일 때 실해되기 때문이다.

예를 들어, A 함수 내부에 B 함수 내부에 C 함수를 선언한 경우, 함수 C의 outerEnviromentReference는 함수 B의 LexicalEnvironment를 참조한다. 함수 B의 LexicalEnvironment에 있는 outerEnviromentReference는 다시 함수 B가 선언되던 때(A)의 LexicalEnviroment를 참조하는 것이다. 이처럼 outerEnvironmentReference는 연결리스트(linked list)형태를 띤다. ‘선언 시점의 LexicalEnvironment’를 계속 찾아 올라가면 마지막엔 전역 컨텍스트의 LexicalEnvironment가 있을 것이다. 또한 각 outerEnvironmentReference는 오직 자신이 선어된 시점의 LexicalEnvironment만 참조하고 있으므로 가장 가까운 요소부터 차례대로만 접근할 수 잇고, 다른 순서로 접근하는 것은 불가능한 것이다. 이런 구조적 특성 덕분에 여러 스코프에서 동일한 식별자를 선언한 경우에는 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에게만 접근 가능하게 되는 것이다.

‘전역 컨텍스트 -> outer 컨텍스트 -> inner 컨텍스트’ 순으로 점차 규모가 작아지는 반면 스코프 체인을 타고 접근 가능한 변수의 수는 늘어난다. 전역 공간에서는 전역 스코프에서 생성된 변수에만 접근할 수 있다. outer 함수 내부에서는 outer 및 전역, inner에서는 모두다.

한편 스코프 체인 상에 있는 변수라고 해서 ㅂ무조건 접근 가능한 것은 아니다. 위 코드 상의 식별자 a는 전역 공간에서도 선언했고 inner 함수 내부에서도 선언했다. inner 함수 내부에서 a에 접근하려고 하면 무조건 스코프 체인 상의 첫 번째 인자, 즉 inner 스코프의 LexicalEnvironment부터 검색할 수밖에 없다. 즉, inner에서 a를 선언 했기에, 전역 공간의 선언한 동일한 이름의 a에 접근할 수 없다. 이를 변수 은닉화(variable shadowing)이라고 한다.

this

실행 컨텍스트의 thisBinding에는 this로 지정된 객체가 저장된다. 실행 컨텍스트 활성화 당시에 this가 지정되지 않은 경우 this에는 전역 객체가 저장된다. 그 밖에는 함수를 호출하는 방법에 따라 this에 저장되는 대상이 다르다.

정리

실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다. 실행 컨텍스트는 전역 공간에서 자동으로 생성되는 전역 컨텍스트와 eval 및 함수 실행에 의한 컨텍스트 등이 있다. 실행 컨텍스트 객체는 활성화되는 시점에 VariableEnvironment, LexicalEnviroment, ThisBinding의 세 가지 정보를 수집한다.

실행 컨텍스트를 생성할 때는 VariableEnvironment와 LexicalEnvironment가 동일한 내용으로 구성되지만 LexicalEnvironment는 함수 실행 도중에 변경되는 사항이 즉시 반영되는 반면 VariableEnvironment는 초기 상태를 유지한다. VariableEnvironment와 LexicalEnvironment는 매개변수명, 변수의 식별자, 선언한 함수의 함수명 등을 수집하는 environmnetRecord와 바로 컨텍스트의 LexicalEnvironmnet 정보를 참조하는 outerEnvironmentReferenece로 구성되어 있다.

호이스팅은 코드 해석을 수월하게 해기위해 environmentRecord의 수집 과정을 추상화한 개념이다. 변수 선언과 값 할당이 동시에 이뤄진 문장은 ‘선언부’만을 호이스팅하고, 할당 과정은 원래 자리에 남아있게 되는데, 여기서 함수 선언문과 함수 표현식의 차이가 발생한다.

스코프는 변수의 유효범위를 말한다. outerEnvironmentReference는 해당 함수가 선언된 위치의 LexicalEnvironment를 참조한다. 코드 상에서 어떤 변수에 접근하려고 하면 현재 컨텍스트의 LexicalEnvironment를 탐색해서 발견되면 그 값을 반환하고, 없다면 outerEnvironemntReference에 담긴 LexicalEnvironment를 탐색하는 과정을 거친다. 전역 컨텍스트의 LexicalEnvironment 까지 탐색해도 없다면 undefined를 반환한다.

전역 컨텍스트의 LexicalEnviroment에 담긴 변수를 전역변수라 하고, 그 빡의 함수에 의해 생성된 실행 컨텍스트의 변수들은 모두 지역변수이다. 안전한 코드 구성을 위해 가급적 전역변수의 사용은 최소화하는 것이 좋다.

this에는 실행 컨텍스트를 활성화 하는 당시에 지정된 this가 저장된다. 함수를 호출하는 방법에 따라 그 값이 달라지는데, 지정되지 않은 경우에는 전역 객체가 저장된다.

업데이트:

댓글남기기