Execution Context

자바스크립트 엔진도 하나의 (거대한) 함수다.

함수?

프로그램을 만드는 개발자가 하는 일은 변수를 선언하고, 여러가지 모듈(함수)를 만들어 하나의 어플리케이션으로 합치는 과정이다.

모듈화를 통해 얻는 이점 중 두 가지는 변수 충돌을 막는다. 지역 변수를 사용해 함수가 실행될 때만 메모리를 사용한다.

함수는 본인의 실행 문맥을 가진다.

우리가 익숙한 함수, call stack 그리고 변수를 먼저 살펴보자. 함수가 실행되면 call stack에 쌓이고 return시 pop 된다. 더불어 함수가 실행되면 실행 문맥(Execution context)이 생긴다.

개발을 하다보면 Context, 혹은 실행 문맥이라는 단어를 많이 접하게 된다.

실행 컨텍스트는 scope, hoisting, this, function, closure 등 Javascript의 동작원리를 설명하는데 핵심이 되는 개념이다.

우선 정의를 살펴보면 다음과 같다.

실행 가능한 코드를 형상화하고 구분하는 추상적인 개념

더 쉽게 정의하자.

실행 가능한 코드가 (실행되기 위해 필요한 or 실행되는) 환경

그렇다면 실행 가능한 코드란 무엇일까?

  1. 전역 코드 : 전역 영역에 존재하는 코드

  2. Eval 코드 : eval 함수로 실행되는 코드

  3. 함수 코드 : 함수 내에 존재하는 코드

자바스크립트 엔진은 코드를 실행하기 위하여 실행에 필요한 여러가지 정보를 알고 있어야 한다.

실행에 필요한 여러가지 정보란 아래와 같은 것들이 있다.

  • 변수 : 전역변수, 지역변수, 매개변수, 객체의 Property

  • 함수 선언

  • 변수의 유효범위(Scope)

  • this

갑작스럽지만, 코드를 보면서 설명을 이어가겠다.

var x = 'xxx';

function foo () {
  var y = 'yyy';

  function bar () {
    var z = 'zzz';
    console.log(x + y + z);
  }
  bar();
}
foo();

위 코드를 실행하면 아래와 같이 실행 컨텍스트 스택(Stack)이 생성하고 소멸한다.

현재 실행 중인 컨텍스트에서 이 컨텍스트와 관련없는 코드 (예를 들어 다른 함수)가 실행되면 새로운 컨텍스트가 생성된다.

이 컨텍스트는 스택에 쌓이게 되고 컨트롤(제어권)이 이동한다.

  1. 전역 코드(Global code)로 컨트롤이 진입하면 전역 실행 컨텍스트가 생성되고 실행 컨텍스트 스택에 쌓인다. 전역 실행 컨텍스트는 애플리케이션이 종료될 때(웹 페이지에서 나가거나 브라우저를 닫을 때)까지 유지된다.

  2. 컨트롤이 실행 가능한 코드로 이동하면 논리적 스택 구조를 가지는 새로운 실행 컨텍스트 스택이 생성된다.

  3. 함수를 호출하면 해당 함수의 실행 컨텍스트가 생성되며 직전에 실행된 코드 블록의 실행 컨텍스트 위에 쌓인다.

  4. 함수 실행이 끝나면 해당 함수의 실행 컨텍스트를 파기하고 직전의 실행 컨텍스트에 컨트롤을 반환한다.


Execution Context의 3가지 객체

실행 컨텍스트는 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념이지만 물리적으로는 객체의 형태를 가지며 아래의 3가지 프로퍼티를 소유한다.

1. Variable Object

실행 컨텍스트가 실행되면 자바스크립트 엔진은 실행에 필요한 여러 정보들을 담을 객체를 생성한다.

이를 Variable Object(이하 VO)라고 한다. VO는 코드가 실행될 때 엔진에 의해 참조되며 코드에서는 접근할 수 없다.

VO는 변수, 매개 변수(parameter)와 인수 정보(arguments), 함수 선언(함수 표현식은 제외)와 같은 정보들을 담는다.

VO는 실행 컨텍스트의 프로퍼티이기 때문에 값을 가지는데, 이 값은 다른 객체를 가리킨다.

그런데 전역 코드 실행시 생성되는 전역 컨텍스트의 경우와 함수를 실행할 때 생성되는 함수 컨텍스트의 경우, 가리키는 객체가 다르다.

이는 전역 코드와 함수의 내용이 다르기 때문이다. 예를 들어 전역 코드에는 매개변수가 없지만 함수에는 매개변수가 있다.

전역 컨텍스트의 경우, VO는 유일하며 최상위에 위치하고 모든 전역 변수, 전역 함수 등을 포함하는 전역 객체(Global Object)를 가리킨다.

전역 객체는 전역에 선언된 전역 변수와 전역 함수를 프로퍼티로 소유한다.

함수 컨텍스트의 경우, VO는 활성 객체(Activation Object)를 가리키며 매개 변수와 인수들의 정보를 배열의 형태로 담는 arguments object가 추가된다.

2. Scope chain

스코프 체인은 일종의 리스트로서 전역 객체와 중첩된 함수 스코프의 레퍼런스를 차례로 저장하고 있다.

다시 말해, 스코프 체인은 해당 전역 또는 함수가 참조할 수 있는 변수, 함수 선언 등의 정보를 담고 있는 전역 객체(GO) 또는 활성 객체(AO)의 리스트를 가리킨다.

현재 실행 컨텍스트의 활성 객체(AO)를 선두로 하여 순차적으로 상위 컨텍스트의 활성 객체(AO)를 가리키며 마지막 리스트는 전역 객체(GO)를 가리킨다.

스코프 체인은 식별자 중에서 객체(전역 객체 제외)의 프로퍼티가 아닌 식별자, 즉 변수를 검색하는 메커니즘이다.

식별자 중에서 변수가 아닌 객체의 프로퍼티(물론 메소드도 포함된다)를 검색하는 메커니즘은 프로토타입 체인(Prototype Chain)이다.

엔진은 스코프 체인을 통해 렉시컬 스코프를 파악한다.

함수가 중첩 상태일 때 하위함수 내에서 상위함수의 스코프와 전역 스코프까지 스코프 체인 검색을 통해 참조할 수 있다.

함수가 중첩되어 있으면 중첩될 때마다 부모 함수의 Scope가 자식 함수의 스코프 체인에 포함된다.

함수 실행중에 변수를 만나면 그 변수를 우선 현재 Scope, 즉 Activation Object에서 검색해보고, 만약 검색에 실패하면 스코프 체인에 담겨진 순서대로 그 검색을 이어가게 되는 것이다.

이것이 스코프 체인이라고 불리는 이유이다.

예를 들어 함수 내의 코드에서 변수를 참조하면 엔진은 스코프 체인의 첫번째 리스트가 가리키는 AO에 접근하여 변수를 검색한다.

만일 검색에 실패하면 다음 리스트가 가리키는 Activation Object(또는 전역 객체)를 검색한다.

이와 같이 순차적으로 스코프 체인에서 변수를 검색하는데 결국 검색에 실패하면 정의되지 않은 변수에 접근하는 것으로 판단하여 Reference 에러를 발생시킨다.

3. this value

this 프로퍼티에는 this 값이 할당된다.

this에 할당되는 값은 함수 호출 패턴에 의해 결정된다.

Java에서의 this는 자기 자신(self)를 가리키는 참조 변수이다.

그러나 javascript에서는 그렇지 않다.

자바스크립트의 경우 Java와 같이 this에 바인딩되는 객체는 한 가지가 아니라 해당 함수 호출 방식에 따라 this에 바인딩되는 객체가 달라진다.

다시 말해, 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되는 것이 아니고, 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다.


Sources …

Last updated