[Javascript] 실행 컨텍스트란 ? (Execution Context)

이번 포스팅은 javascript의 실행컨텍스트와 호이스팅 그리고 스코프체이닝에 대해서 공부한 내용들을 정리할 것입니다.
(본 포스팅은 코어자바스크립트 책을 기반으로 작성되었습니다.)

1.실행 컨텍스트(Execution Context)?

실행 컨텍스트(Execution Context)는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체입니다.

javascript는 코드를 실행하며, 필요한 환경정보들을 (ex. 변수, 함수 등..)들을 모아 이를 이용해 실행 컨텍스트를 만들고, 이를 콜 스택(call stack)에 쌓아올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체코드의 환경과 순서를 보장합니다.

1.1 실행 컨텍스트가 생성되는 경우

"동일한 환경" 즉, 하나의 실행 컨텍스트를 구성할 수 있는 방법으로는
1. 전역공간 (javascript 실행 시작시)
2. eval() 함수
3. 함수 실행시

자동으로 생성되는 전역공간과 악마로 취급받는 eval(악마로 취급받는 이유)을 제외하면 우리가 흔히 실행 컨텍스트를 구성하는 방법은 함수를 실행하는 것뿐입니다! (선언되는 시점이 아닌, 함수가 실행(호출)되는 시점입니다.)

1.2 실행 컨텍스트와 코드의 실행과정

// ---------------- (1)
var a = 1;
function outer() {
	function inner() {
    	console.log(a) // undefined
        var a = 3;
    }
    inner() // ---------------- (2)
    console.log(a) // 1
}
outer() // ---------------- (3)
console.log(a) // 1
  1. (1)에서 전역컨텍스트가 생성되고, 콜스택에 쌓습니다.(전역 컨텍스트라는 개념은 일반적인 실행 컨텍스트와 특별히 다를 것이 없습니다. 단지, 최상단의 공간은 코드 내부에서 별도의 실행 명령이 없어도 브라우저에서 자동으로 실행하므로 자바스크립트 파일이 열리는 순간 전역 컨텍스트가 활성화된다고 이해하면 됩니다.)
  2. 다시 코드를 실행하며 (3)에서 outer()가 호출되고, outer()함수 실행을 위한 실행 컨텍스트가 생성되고, 콜스택에 쌓입니다.
    콜스택의 맨 위에 outer 실행 컨텍스트가 놓인 상태가 됐으므로 전역 컨텍스트와 관련된 코드의 실행을 일시중단하고 outer 실행 컨텍스트와 관련된 코드, 즉 outer 함수 내부의 코드들을 순차로 실행합니다.
  3. outer()함수가 실행되고 코드가 실행되다가, (2)에서 inner()함수가 호출되며, inner()함수 실행을 위한 실행 컨텍스트가 생성되고 콜스텍에 쌓입니다.
  4. inner()함수 안에서 a를 콘솔에 출력하고 나면 실행할 코드가 더이상 없기 때문에, inner()가 콜스택에서 제거됩니다.
  5. 그 다음 콜스택의 최상단의 위치한 outer()함수가 다시 실행되며, (2)아래의 console.log(a)가 실행되고, a가 콘솔에 출력됩니다.
    outer() 함수도 콜스택에서 제거됩니다.
  6. 콜스택 최상단에 위치한 전역 컨텍스트가 다시 실행되며, 마지막 줄이 실행되면서 a를 다시 콘솔에 출력하고 종료합니다.

2.실행 컨텍스트의 구성

실행 컨텍스트 객체에는 아래 정보들이 담기게됩니다.

  • VariableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보. 선언 시점의 LexicalEnvironment의 스냅샷으로, 변경사항은 반영되지 않음
    • environmentRecord
    • outer-EnvironmentReference
  • LexicalEnvironment : 처음에는 VariableEnvironment와 같지만 변경사항이 실시간으로 반영됨
    • environmentRecord
    • outer-EnvironmentReference
  • ThisBinding : this 식별자가 바라보고 있는 대상 객체

VariableEnvironment에 담기는 내용은 LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지한다는 점이 다릅니다. 실행 컨텍스트를 실행할 때 VariableEnvironment에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LexicalEnvironment를 만들고, 이후에는 LexicalEnvironment와를 주로 활용하게 됩니다. 일반적으로 함수의 LexicalEnvironment는 해당 함수가 가지는 자신의 로컬 스코프 범위를 말합니다.

이 두 환경의 내부는 다시 EnvironmentRecord 와 OuterEnvironmentReference 로 구성되어 있습니다.

EnvironmentRecord : 컨텍스트와 관련된 코드의 식별자 정보들이 저장됨
OuterEnvironmentReference : 호출된 함수가 선언될 당시의 Lexical Environment를 참조하는 포인터로, 스코프 체인을 가능하게 함

3. EnvironmentRecord의 구성

EnvironmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됩니다.

var a = 3; // 여기서 식별자는 a를 말합니다. 

컨텍스트 전체를 처음부터 끝까지 쭉 훑어나가면서 순서대로 식별자들을 수집합니다. 여기에서 수집되는 식별자들은 매개변수 식별자, 선언된 함수, var로 선언된 변수의 식별자 등이 해당됩니다.

function foo(x) {
  console.log(x);
  var a = 1;
  var b = 2;
  function boo() {
  console.log("work")
  }
}
foo(1)

위의 코드에서, 실행 컨텍스트의 EnvironmentRecord에는 식별자 x, a,b와 함수 foo가 수집됩니다. EnvironmentRecord는 현재 실행될 컨텍스트의 대상 코드 내에 어떤 식별자들이 있는지만을 먼저 수집하기 때문에, 변수를 인식할 때 식별자만 끌어올리고 할당 과정은 원래 자리에 순서대로 남겨둡니다.

"코드가 실행되기 전임에도 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명들을 모두 알 수 있다" => 호이스팅(hoisting)의 개념입니다.

function a (x) {
  var x; // 매개변수를 변수 선언/할당과 같다고 가정하고 변환하면...
  var a;
  var b;
  function foo() {
    console.log('work');
  };
  
  x = 1;
  console.log(x);
  a = 1;
  b = 2;
}

a(1);

위 코드처럼 식별자가 먼저 수집되고, 그 뒤에 순서대로 코드를 실행한다는 예시는 호이스팅을 설명할 때 자주 볼 수 있는 사례이다.

4. 스코프, 스코프체인, OuterEnvironmentReference

OuterEnvironmentReference를 이해하기 위해서는 스코프개념을 먼저 이해해야 합니다. 스코프란, 식별자에 대한 유효범위입니다. 자바스크립트에는 전역 스코프 (global scope)와 지역 스코프(local scope)가 있습니다.

var a = 1;
function scope() {  // 함수 스코프
  var b = 2;
  console.log(a);  // 1
  console.log(b);  // 2
}
console.log(a);  // 1
console.log(b);  // b is not defined

scope 함수 외부에서 선언한 변수 a는 함수 scope안에서도 접근이 가능하지만, 함수 안에서 선언한 변수 b는 오직 함수 안에서만 접근 할 수 있다. 변수 a는 전역 스코프에, 변수 b는 지역스코프에 있기 때문입니다.
자바스크립트는 변수의 유효 범위를 검색할 때 안에서부터 바깥으로 찾아나가는데, 이것을 스코프 체인(scope chain)이라고 합니다.
따라서 전역 스코프에 선언된 변수들은 어느 곳에서도 접근이 가능합니다. 반면 지역 스코프는 선언된 함수의 내부에서만 접근이 가능합니다.

OuterEnvironmentReference는 현재 호출된 함수가 선언되는 시점에서의 Lexical Environment를 참조하는 포인터이다. OuterEnvironmentReference는 연결리스트 형태를 띄며 ‘선언 시점의 LexicalEnvironment’를 계속 찾아 올라간다. '선언 시점의 LexicalEnvironment'라는건 결국 해당 함수가 속한 상위 스코프의 범위이다.
각 함수의 OuterEnvironmentReference는 오직 자신이 선언된 시점의 LexicalEnvironment만 참조하고 있기 때문에 가장 가까운 요소부터 위로 차례대로만 접근할 수 있다.

var a = 1;

var outer = function () {
  var inner = function () {
    console.log(a); // undefined 출력 (a가 LexicalEnvironment에서 발견되지만, 값이 할당되기 전)
    var a = 3;
  };
  
  inner();
  console.log(a); // 1 출력 (outerEnvironmentReference에서 발견한 값 출력)
};

outer();
console.log(a); // 1 출력 (LexicalEnvironment에서 발견한 값 출력)

전역 컨텍스트

  • environmentRecord: { a, outer } 식별자 저장
  • outerEnvironmentReference : 아무것도 담겨있지 않음

outer 실행 컨텍스트

  • environmentRecord: { inner } 식별자 저장
  • 전역에서 선언되었기 때문에, 전역 컨텍스트의 LexicalEnvironment 참조 복사
  • outerEnvironmentReference : [ GLOBAL, { a, outer } ] 표기
    • 첫번째는 실행 컨텍스트 이름, 두번째는 environmentRecord 객체

inner 실행 컨텍스트

  • environmentRecord: { a } 식별자 저장
  • outer 함수 내부에서 선언됐으므로 outer 함수의 LexicalEnvironment 참조 복사
  • outerEnvironmentReference : [ outer, { inner } ]

5. ThisBinding

실행 컨텍스트의 thisBinding에는 this로 지정된 객체가 저장됩니다. 실행 컨텍스트 활성화 당시에 this가 지정되지 않은 경우 this에는 전역 객체가 저장됩니다. 그밖에는 함수를 호출하는 방법에 따라 this에 저장되는 대상이 다릅니다. 이에 대해서는 나중에 ! "this"를 심도 있게 파볼 예정입니다.

6. 정리

자바스크립트에서 실행 컨텍스트란, 코드를 실행하기 위해 필요한 정보들을 가진 범위를 객체 형태로 나타낸 것이다. 실행 컨텍스트를 구성하는 LexicalEnvironment는 현재의 실행 컨텍스트가 실행되기 위한 여러 정보를 담고 있다. LexicalEnvironment는 식별자들에 대한 정보를 담은 EnvironmentRecord와, 상위 LexicalEnvironment를 참조해 스코프 체인을 가능하게 하는 OuterEnvironmentReference 정보로 구성되어 있다. EnvironmentRecord는 식별자 바인딩을 관리하고, binding object라 불리는 특정 객체의 속성으로 선언된 식별자들을 관리한다. OuterEnvironmentReference는 현재의 실행 컨텍스트를 구성한 함수가 선언되는 시점에서의 상위 LexicalEnvironment를 참조하기 때문에 식별자의 유효 범위를 상위로 거슬러 올라가 찾게 되는 스코프 체인이 가능하다.

좋은 웹페이지 즐겨찾기