실행 컨텍스트

정수·2023년 3월 10일
2

JavaScript

목록 보기
6/15
post-thumbnail

실행 컨텍스트 (execution context)

실행 컨텍스트란 실행할 코드에 제공할 환경 정보들을 모아놓은 객체입니다. 자동으로 생성되는 전역공간과 악마로 취급받는 eval을 제외하면 흔히 실행 컨텍스트를 구성하는 방법은 함수를 실행하는 것뿐입니다.

기본적으로 Javascript의 동작 원리를 살펴보면 아래와 같습니다.
1. 실행 컨텍스트가 활성화(함수 실행)가 됩니다.
2. 해당 컨텍스트에 관련된 코드들을 실행하는데 필요한 환경 정보들을 수집해서 실행 컨텍스트 객체에 저장합니다.

  • 선언된 변수(식별자)를 위로 끌어올립니다. (hoisting)
  • 외부 환경 정보를 구성합니다.
  • this 값을 설정하는 등의 동작을 수행합니다.

객체에 담기는 정보는 아래와 같습니다.

  • VariableEnvironment
    • 현재 컨텍스트 내의 식별자들에 대한 정보(environmentRecord) + 외부 환경 정보(outerEnvironmentReference)가 저장됩니다.
    • 선언 시점의 LexicalEnvironment의 스냅샷입니다. 변경 사항은 반영되지 않습니다.
  • LexicalEnvironment
    • VariableEnvironment와 동일한 구성이기 때문에 초기화 과정 중에는 사실상 완전 동일하지만 변경 사항이 실시간으로 반영된다는 차이점이 있습니다.
    • "현재 컨텍스트 내부에는 a, b, c와 같은 식별자들이 있고 그 외부 정보는 D를 참조하도록 구성한다" 와 같은 사전적 환경을 나타냅니다.
  • ThisBinding
    • this 식별자가 바라봐야 할 대상 객체입니다.

environmentRecord & hoisting

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

변수 정보는 코드 실행 전에 모두 수집되며, Javascript Engine은 이미 변수명을 모두 알고 코드를 실행하게 되는 것입니다.

이러한 과정을 식별자를 최상단으로 끌어올린 다음 코드를 실행한다고 생각할 수 있고 이렇게 끌어올린 변수 정보를 수집하는 과정을 호이스팅(hoisting)이라고 합니다.

전역 실행 컨텍스트

전역 실행 컨텍스트는 변수 객체를 생성하는 대신 전역 객체(global object)를 활용하며 브라우저의 window, Node.js의 global 객체 등이 존재합니다. 이들은 Javascript 내장 객체(native object)가 아닌 호스트 객체(host object)로 분류됩니다.

hoisting 개념 이해하기

아래 예제들의 콘솔이 어떻게 찍히는지 맞춰보세요!

  • 예제 01
    function a(x) {
      console.log(x); // (1)
      var x;
      console.log(x); // (2)
      var x = 2;
      console.log(x); // (3)
    }
    a(1)
  • 예제 02
    function a(x) {
      console.log(b); // (1)
      var b = 'bbb';
      console.log(b); // (2)
      function b() { }
      console.log(b); // (3)
    }
    a()

정답을 한번 살펴봅시다 :)
이해를 위해 코드를 살짝 바꾸겠지만 실제로 Javascript Engine은 코드를 바꿔서 실행하진 않습니다.

  • 예제 01 - 풀이
    쉽게 이해하기 위해 매개변수 식별자도 끌어올려짐의 대상이기 때문에 변수로 선언하는 코드로 변환하겠습니다. 동작하는 원리는 동일합니다.

    function a() {
      var x = 1;
      console.log(x); // (1)
      var x;
      console.log(x); // (2)
      var x = 2;
      console.log(x); // (3)
    }
    a()

    여기서 변수 선언 부분을 실행 컨텍스트 내부의 최상단으로 끌어올리겠습니다.

    function a() {
      var x;
      var x; // 이미 선언된 변수가 있으므로 무시합니다.
      var x; // 이미 선언된 변수가 있으므로 무시합니다.
      
      x = 1;
      console.log(x); // (1)
      console.log(x); // (2)
      x = 2;
      console.log(x); // (3)
    }
    a()

    그럼 자연스럽게 (1) 1, (2) 1, (3) 2 라는 결과가 도출됩니다.

  • 예제 02 - 풀이
    식별자들을 끌어올려봅시다.

    function a() {
      var b; // 변수는 선언부만 끌어올립니다.
      function b () {} // 함수 선언은 함수 전체를 끌어올립니다.
      
      console.log(b); // (1)
      b = 'bbb';
      console.log(b); // (2)
      console.log(b); // (3)
    }
    a()

    이 또한 자연스럽게 (1) b 함수, (2) 'bbb', (3) 'bbb' 라는 결과가 도출됩니다.

outerEnvironmentReference & scope

스코프(scope)식별자에 대한 유효범위입니다. 예를 들어 함수 A의 외부에서 선언한 변수는 A의 외부뿐 아니라 내부에서도 접근이 가능하지만, A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근할 수 있습니다.

이러한 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것스코프 체인(scope chain)이라고 합니다.

그리고 이를 가능하게 하는 것이 바로 outerEnvironmentReference 입니다.

예시 코드를 통해 더 자세하게 설명을 드리겠습니다.

var a = 1;
var outer = function () {
  var inner = function () {
    console.log(a); // (1)
    var a = 3;
  };
  inner();
  console.log(a); // (2)
};
outer();
console.log(a); // (3)

예시 코드의 흐름에 따라 설명을 덧붙이겠습니다.

단축어 정리

L.E : LexicalEnvironment
e : environmentRecord
o : outerEnvironmentReference

  1. 전역 컨텍스트 활성화
    1-1. 전역 컨텍스트의 e{ a, outer } 식별자 저장
    1-2. 변수 a에 1을, outer에 함수 할당

  2. outer 함수 호출
    2-1. 전역 컨텍스트 임시중단
    2-2. outer 실행 컨텍스트 활성화
    2-3. outer 실행 컨텍스트의 e{ inner } 식별자 저장
    2-4. outer 실행 컨텍스트의 o에 outer 함수가 선언될 당시의 L.E[ GLOBAL, { a, outer } ]를 참조복사 (첫 번째는 실행 컨텍스트의 이름, 두 번째는 e 객체)
    2-5. outer 스코프에 있는 변수 inner를 할당

  3. inner 함수 호출
    3-1. outer 실행 컨텍스트 임시중단
    3-2. inner 실행 컨텍스트 활성화
    3-3. inner 실행 컨텍스트의 e{ a } 식별자 저장
    3-4. inner 실행 컨텍스트의 o[ outer, { inner } ]를 참조복사
    3-5. 식별자 a에 접근

    3-5-1. 현재 활성화 상태인 inner 컨텍스트의 e에서 a 검색
    3-5-2. 할당된 값이 없기에 undefined 출력
    3-5-3. inner 스코프에 있는 변수 a에 3을 할당

    3-6. inner 함수 실행 종료 (inner 실행 컨텍스트가 콜 스택에서 제거)

  4. outer 함수 다시 활성화
    4-1. 식별자 a에 접근

    4-1-1. 현재 활성화 상태인 outer 컨텍스트의 L.E 접근 후 e에서 a 검색
    4-1-2. 찾지 못하여 outer 컨텍스트의 L.E 접근 후 o에서 a 검색 & 1 출력

    4-2. outer 함수 실행 종료 (outer 실행 컨텍스트가 콜 스택에서 제거)

  5. 전역 컨텍스트 다시 활성화
    5-1. 식별자 a에 접근

    5-1-1. 현재 활성화 상태인 전역 컨텍스트의 L.E 접근 후 e에서 a 검색
    5-1-2. 할당된 값이 있기에 1 출력

    5-2. 전역 컨텍스트 종료 & 콜 스택에서 제거

  6. 종료

복잡해 보여도 순서대로 따라오시면 큰 어려움 없이 이해하실 수 있습니다.

변수 은닉화(variable shadowing)

전역 공간에서 a라는 변수를 선언하고 값을 할당했지만 inner 함수 내부에서도 a라는 변수를 선언했기 때문에 전역 공간에서 선언한 동일한 이름의 a 변수에는 접근이 불가했습니다. 이러한 현상을 변수 은닉화라고 합니다.

참고 자료


profile
해피한하루

0개의 댓글