실행 컨텍스트


자바스크립트가 실행되는 환경

실행 컨텍스트의 종류

  • Global Execution Context: 전역 실행 컨텍스트는 처음으로 자바스크립트 코드가 실행될 때 생성되는 실행 컨텍스트이다. 이 실행 컨텍스트에서는 전역에서 관리되는 값들(함수나, 별도 모듈로 분리되어 있지 않은 값)을 관리한다.
  • Function Execution Context: 함수 실행 컨텍스트는 함수가 호출될 때 마다 생성되는 실행 컨텍스트이다. 각각의 함수 호출은 모두 각자의 실행 컨텍스트를 생성하고 가진다.
  • 그 외에도 Eval Execution Context와 Module Execution Context가 존재한다.
  • 콜스택

  • 자바스크립트 엔진에는 코드 실행순서를 관리하는 콜스택이 존재한다.
  • 자바스크립트는 스택에 실행 컨텍스트를 순차적으로 추가하고 제거하면서 코드의 순서를 관리한다.
  • 따라서 아래 코드의 경우 다음과 같은 순서로 실행된다.
  • const a = 'a';
    function foo() {
    const b = 'b';
    bar();
    function bar() {
    const c = 'c';
    if(true){
    console.log("Hello, World")
    }
    }
    foo();
  • 비어 있는 상태에서 시작
  • 전역 실행 컨텍스트 생성 (전역 스코프에 선언된 변수, 함수 등의 정의)
  • → 위 코드에서는 변수 a와 foo 함수가 담긴다.

  • foo 함수가 호출되면서 foo 함수의 함수 실행 컨텍스트 생성
  • → 변수 b와 bar 함수

  • bar 함수가 호출되면서 bar 함수의 실행 컨텍스트 생성
  • bar 함수의 동작 완료 후 bar 제거
  • foo 함수의 동작 완료 후 foo 제거
  • 모든 코드의 실행이 종료, 전역 컨텍스트 제거
  • 모든 실행 컨텍스트는 평가실행의 과정으로 생성되고 관리된다.
  • 평가
  • 실행
  • 렉시컬 환경

  • 자바스크립트의 실행 컨텍스트에는 현재 컨텍스트 내의 식별자들에 대한 정보와 외부 환경 정보를 담는 Variable Environment와 이를 복사하여 가진 후 변경 사항이 실시간으로 반영되는 Lexical Environment가 존재한다.
  • 자바스크립트 엔진의 콜 스택이 코드의 실행 순서를 관리한다면, 렉시컬 환경은 스코프와 식별자를 관리한다.
  • 렉시컬 환경은 키와 값을 갖는 객체 형태의 스코프를 생성하여 식별자를 키로 등록하고 식별자에 바인딩된 값을 관리한다.
  • 예를 들어, 자바스크립트에서 전역 변수를 호출하면 전역 컨텍스트 안에 있는 이 렉시컬 환경 안에 담긴 변수를 탐색하게 된다.
  • 아래는 렉시컬 환경의 모습이다.
  • const GlobalExecutionContext = {
    GlobalLexicalEnvironment: {
    GlobalEnvironmentRecord: {
    ObjectEnvironmentRecord: {
    BindingObject: global,
    },
    DeclarativeEnvironmentRecord: {
    a: "a"
    },
    GlobalThisValue:global,
    },
    OuterLexicalEnvironmentReference:null
    },
    };
  • LexicalEnvironemnt는 평가과정에서 일반적으로 크게 3가지의 동작을 수행한다.
  • 실행과정에서는 평과과정에서 생성된 정보들을 기반으로 값들을 변경하는 방식으로 진행된다.
  • LexicalEnvironment는 실행 컨텍스트의 종류에 따라 조금씩 다르지만 크게 두가지 영역으로 구성되어 있다.
  • EnvironmentRecord 안에는 다음과 같은 것들이 있다.
  • 위쪽 렉시컬 환경은 전역 렉시컬 환경의 모습이라 OuterLexicalEnvironmentReference가 null이지만, 만약 전역에서 선언한 함수가 있다면 아래와 같은 모습이다.
  • const FooFunctionExecutionContext = {
    LexicalEnvironment: {
    FunctonEnvironmentRecord: {
    b: "b",
    ThisValue: global,
    },
    OuterEnvironmentReference: GlobalExecutionContext.GlobalLexicalEnvironment
    },
    };
  • 위처럼 OuterEnvironmentReference가 상위 스코프인 global의 렉시컬환경을 참조하게 된다.
  • 이러한 동작으로 인해 클로저라는 개념이 성립하게 된다.
  • 클로저

  • 클로저는 외부 환경을 기억하고 있는 함수이다.
  • 실행 컨텍스트는 해당 컨텍스트안의 모든 코드를 실행하고 나면 콜스택에서 제거되지만, 실행 컨텍스트가 제거된다고 반드시 렉시컬 환경이 제거되는 것은 아니다.
  • 렉시컬 환경은 실행 컨텍스트에 의해서 참조되기는 하지만, 독립적인 객체로서 그 누구에게도 참조되지 않을 때 비로소 가비지 컬렉팅의 대상이 되어서 메모리에서 소멸된다.
  • 따라서 외부 함수의 실행 컨텍스트 내에서 정의된 내부함수가 외부 함수의 LexicalEnvironment를 계속해서 참조하고 있다면 외부 함수의 LexicalEnvironment는 소멸하지 않는다.
  • 이를 이용하여 아래와 같은 클로저 함수가 탄생할 수 있다.
  • function makeAddNumFunc(num) {
    const toAdd = num
    return function (num) {
    return num + toAdd
    }
    }
    const add5 = makeAddNumFunc(5)
    add5(3) // 8
    add5(8) // 13
    add5(15) // 20
  • 위 상황에서 add5는 자신이 생성될 때의 외부 환경을 기억하는 클로저 함수이다.
  • 클로저는 위처럼 상태가 의도치 않게 변경되지 않도록 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하여 상태를 안전하게 변경하고 유지하기 위해 사용한다.
  • useState나 useEffect 등 리액트 훅도 이러한 클로저의 원리를 이용하여 동작한다.