본문 바로가기
개발

실행 컨텍스트 (Excution Context)

by FODI 2022. 9. 25.

실행 컨텍스트는 코드가 실행되는 환경에 대한 데이터를 모아놓은 추상적인 개념이다. 

 

무슨 말인지 명확하게 와닿지 않으니 '코드가 실행되는 환경'에 대해 먼저 구체화시켜 보자. 

 

아래의 코드에는 전역에서 선언된 outer함수와 outer함수 내부에서 선언된 inner함수가 있다. 그리고 outer함수 내부에는 변수 a가 let으로 선언되어 있고, 변수 b는 const로 선언되어 있으며, inner함수에는 변수 c와 d가 각각 선언되어 있다. 이것은 코드가 실행되는 환경이자 맥락(context)의 극히 일부분이다.

만약 코드가 실행되는 맥락을 모르는 사람이 아래의 코드를 본다면 이해할 수 있을까?

 

function outer() {
  let a = 'a',
  const b = 'b',
  
  function inner() {
    let c = 'c',
    const d = 'd',
  }
}

 

마찬가지로 JS 또한 코드를 실행하기 위해서는, 코드의 맥락을 파악해야 한다. 이를 위해서 JS는 코드를 읽으며 코드의 맥락을 파악할 수 있는 데이터(scope, this 등)를 분류하고 저장하는데, 이것이 바로 실행 컨텍스트이다.

 

 

실행 컨텍스트는 3가지 종류가 있다.

전역 실행 컨텍스트와 함수 실행 컨텍스트, 그리고 Eval 함수 실행 컨텍스트.

 

전역 실행 컨텍스트는 전역(Global)에 선언된 변수와 함수에 대한 환경 데이터를 모아놓은 실행 컨텍스트로, 하나의 프로그램에서 하나만 존재한다. 함수 실행 컨텍스트는 각각의 함수에 대한 환경 데이터를 모아놓은 실행 컨텍스트로, 함수가 호출될 때 생성된다. Eval 함수 실행 컨텍스트는 ECMA에서 사용을 권장하지 않으므로 별도의 설명은 생략한다.

 

모든 실행 컨텍스트는 Lexical Envrionment와 Variable Environment로 구성되어 있는데, 저장되는 데이터의 종류가 다를 뿐 크게 차이는 없으니 Variable Environment는 제외하고 Lexical Environment에 대해 알아보자.

 

Lexical Environment는 선언된 함수와 변수에 대한 환경 데이터(변수를 선언한 방법, 함수를 선언한 방법 등)가 저장되는 Environment Records, 스코프 탐색과 관련된 정보가 저장되는 reference of the outer environment, 마지막으로 this값이 저장되는 this binding 영역으로 구성되어있다.

 

참조하는 변수가 해당 함수 내부에 없을 때, 외부 함수의 Lexical Environment에 해당 변수에 대한 데이터가 있는지 확인하는 과정을 스코프 체이닝이라고 하는데, 이 때 내부 함수에서는 outer Lexical environment(외부 함수의 환경 데이터)에 대한 정보가 저장되어 있는 reference of the outer environment 영역을 통해 외부 함수에 접근한다.

 

function outer() {
  let a = 'outer'
  
  function inner() {
    let b = a;
    console.log(b) // outer
  }
  
  return inner()
}

outer()

// inner 함수의 Lexical environment에서 변수를 찾을 수 없기 때문에,
// inner 함수의 reference of the outer environment에 저장되어 있는 정보를 기반으로
// outer 함수의 Lexical envrionment에서 a 변수를 찾는다.
// 이 과정이 스코프 체이닝이다.

 

스코프 체이닝 얘기가 나온 김에 아래의 코드에 대해서도 기록으로 남겨두려 한다. 이전에 JavaScript가 변수에 값을 저장하는 방법에서 선언과 초기화에 대해 얘기하면서 호이스팅을 언급했었는데, 스코프 체이닝과 맞물리면 조금 더 복잡한 상황을 연출할 수 있다. 아래의 코드에서 Reference Error가 발생하는 것처럼 말이다.

 

function outer() {
  let a = 'outer'
  
  function inner() {
    let b = a;
    console.log(b) // Reference Error
    
    let a = 'inner'
    console.log(b) // inner
  }
  
  return inner()
}

outer()

 

* Variable Environment에는 var로 선언된 변수에 대한 정보만 저장이 된다.

 

 

마지막으로, 실행 컨텍스트가 어떻게 생성되는지 생성 과정에 대해 알아보자.

 

실행 컨텍스트의 생성 과정은 Creation phase와 Execution phase로 나뉜다. 코드를 읽기 시작하는 Creation phase에서는 실행 컨텍스트 생성하여 변수와 함수, 인자(arguments)를 저장(변수와 함수에 대한 선언과 초기화 진행)하고, Execution phase에서는 값이 할당된다. 단, 함수는 호출되는 시점에서 함수 실행 컨텍스트(Functional Execution Context)가 생성(Creation phase)된다.

 

아래의 코드를 실행할 때, 각각의 실행 컨텍스트가 phase에 따라 어떻게 달라지는지 살펴보자.

 

let a = 20;
const b = 30;
var c;

function multiply(e, f) {
  var g = 20;
  return e * f * g;
}

c = multiply(20, 30);

 

Creation phase에서는 전역 실행 컨텍스트가 생성되고, 선언된 모든 코드가 전역 실행 컨텍스트의 Lexical Environment에 저장된다. (var는 Variable Environment에 저장된다.)

 

// creation phase

GlobalExectionContext = {
  LexicalEnvironment: { // let과 const로 선언한 변수
    EnvironmentRecord: { // 변수와 함수에 대한 데이터 저장
      Type: "Object",
      a: uninitialized, // let과 const는 초기화가 진행되지 않았다.
      b: uninitialized,
      multiply: func
    }
    outer: null, // 전역 실행 컨텍스트에는 outer Lexical Environment가 존재하지 않는다.
    ThisBinding: Global Object
  },

  VariableEnvironment: { // var로 선언한 변수
    EnvironmentRecord: {
      Type: "Object",
      c: undefined, // var는 초기화가 완료되었기 때문에 undefined가 저장되었다.
    }
    outer: null,
    ThisBinding: Global Object
  }
}

 

Execution phase에서는 각각의 변수에 할당된 값(a = 20, b = 30, c = undefined)이 입력된다.

 

// execution phase

GlobalExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      a: 20,
      b: 30,
      multiply: func
    }
    outer: null,
    ThisBinding: Global Object
  },

  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      c: undefined,
    }
    outer: null,
    ThisBinding: Global Object
  }
}

 

multiply함수는 호출되는 시점에 Creation phase가 진행되어 함수 실행 컨텍스트를 생성한다.

 

// creation phase

FunctionExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      Arguments: { 0: 20, 1: 30, length: 2 }, // multiply 함수의 인자
    },
    outer: GlobalLexicalEnvironment,
    ThisBinding: Global Object or undefined,
  },

  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      g: undefined // 초기화가 완료되었다.
    },
    outer: GlobalLexicalEnvironment,
    ThisBinding: Global Object or undefined
  }
}

 

Execution phase에서 변수 g에 값이 할당되는 것을 확인할 수 있다.

 

// execution phase

FunctionExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      Arguments: { 0: 20, 1: 30, length: 2 },
    },
    outer: GlobalLexicalEnvironment,
    ThisBinding: Global Object or undefined,
  },

  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      g: 20 // 값이 할당되었다.
    },
    outer: GlobalLexicalEnvironment,
    ThisBinding: Global Object or undefined
  }
}

 

 

참고

 

ECMAScript 2015 Language Specification – ECMA-262 6th Edition

5.1.1 Context-Free Grammars A context-free grammar consists of a number of productions. Each production has an abstract symbol called a nonterminal as its left-hand side, and a sequence of zero or more nonterminal and terminal symbols as its right-hand sid

262.ecma-international.org

 

Understanding Execution Context and Execution Stack in Javascript

Understanding execution context and stack to become a better Javascript developer.

blog.bitsrc.io

 

'개발' 카테고리의 다른 글

React Context  (0) 2022.09.28
React Router v6  (0) 2022.09.25
JavaScript에서의 함수  (0) 2022.09.25
JavaScirpt가 변수에 값을 저장하는 방법  (0) 2022.09.24
프론트엔드 개발자의 길을 걷게 된 계기  (0) 2022.09.24

댓글