ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Javascript] 실행 컨텍스트
    Javascript 2023. 3. 17. 09:57

    들어가기 전에..

    자바스크립트 프로그램을 요술 기계처럼 이리 누르고, 저리 눌러가면서 작동을 한다. 오늘 글에서는 이러한 현상의 뒷면에 있는 자바스크립트 내부 구조를 설명한다. 자바스크립트에서 가장 중요한 핵심 개념 중 하나라고 생각한다.

    실행 가능한 코드 (Executable Code)

    자바스크립트 엔진은 실행 가능한 코드를 만나면 코드를 평가하여 => 실행 컨텍스트(Executable Context)로 만든다.

    실행 가능한 코드

    • 전역 코드: 전역 객체 WIndow 아래에 정의된 함수         ==> 전역 실행 컨텍스트(Global Execution Context)
    • 함수 코드: 함수                                                             ==> 함수 실행 컨텍스트(Functional Execution Context)
    • eval 코드: eval 함수 (단, eval 코드는 렉시컬 환경이 아니라 별도의 동적 환경에서 실행된다. eval 함수 자체가 사용되는 경우도 극히 드물고, 사용해본적도 없기 때문에 생략...)

    실행 컨텍스트(Execution Context)의 구성

    실행 컨텍스트는 가장 중요한 렉시컬 환경(Lexical Environment) 컴포넌트, 변수 환경(Variable Environment) 컴포넌트, 디스 바인딩(This Binding) 컴포넌트로 나누어 관리하도록 만들어졌다. 아래는 자바스크립트의 객체 표현을 빌린 구조이다.

    ExecutionContext= {
    	// 렉시컬 환경 컴포넌트
        LexicalEnvironment: {},
        // 변수 환경 컴포넌트
        VariableEnvironment: {},
        // 디스 바인딩 컴포넌트
        ThisBinding: null
    }

     

    실행 컨텍스트의 개념을 공부하던 중, 가장 혼란스러웠던 것은 실행 컨텍스트의 구성을 변수 객체, Scope Chain, This 프로퍼티로 설명이 되어 있는 것과 렉시컬 환경, 변수 환경, 디스 바인딩 컴포넌트로 설명이 되어 있는 것.... 어느 개념이 정확하냐 혼란스러웠다.

    ECMAScript 5 이후 부터는 렉시컬 환경 컴포넌트가 변수 객체와 Scope Chain을 포함하는 개념이 되었다. 따라서,  실행 컨텍스트의 구성을 컴포넌트 별로 설명 한다.

     

    렉시컬 환경 컴포넌트와 변수 환경 컴포넌트

    렉시컬 환경 컴포넌트와 변수 환경 컴포넌트는 앞으로 설명할 렉시컬 환경 타입의 컴포넌트이며, 렉시컬 환경 컴포넌트와 변수 환경 컴포넌트는 타입이 같고 실제로 with 문을 사용할 때를 제외하면 내부 값이 같으므로 렉시컬 환경 컴포넌트로 통일해서 설명한다.

    디스 바인딩 컴포넌트

    디스 바인딩 컴포넌트는 그 함수를 호출한 객체의 참조가 저장이 되는 곳이다. 이것이 가리키는 값이 곧 해당 실행 문맥의 this가 된다.

    렉시컬 환경 컴포넌트의 구성

    자바스크립트 엔진이 자바 스크립트 코드를 실행하기 위해 자원을 모아 둔 곳으로 구체적으로는 함수 또는 블록의 유효범위 안에 있는 식별자와 그 결괏값이 저장되는 곳이다. 자바스크립트 엔진은 해당 자바스크립트 코드의 유효 범위 안에 있는 식별자와 그 식별자가 가리키는 값을 키와 값의 쌍으로 바인드해서 렉시컬 환경 컴포넌트에 기록한다.

    LexicalEnvironment: {
    	// 환경 레코드 (ECMAScript 3의 변수 객체와 비슷한 역할)
        EnvironmentRecord: {},
    	// 외부 렉시컬 환경 참조 (ECMAScript 3의 Scope Chain과 비슷한 역할)
        OuterLexicalEnvironment Refrenece: {}
    }

    환경 레코드

    • 유효 범위 안에 포함된 식별자(Identifier)를 기록하고 실행하는 영역
    • 자바스크립트 엔진은 유효 범위 안의 식별자와 결괏값을 바인드해서 환경 레코드에 기록
    • 변수, 함수, argument 선언들을 기록

    외부 렉시컬 환경 참조

    자바스크립트 엔진은 유효 범위 너머의 유효 범위도 검색할 수 있다. 외부 렉시컬 환경 참조에는 함수를 둘러싸고 있는 코드가 속한 렉시컬 환경 컴포넌트의 참조가 저장된다.

    • Outer를 통한 식별자 결정(Identifier Resolution)을 가능하게 한다.
    • 해당 function environment의 부모 함수를 참조한다. === 상위 스코프에 접근할 수 있다.
    var a = 100;
    
    function foo() {
    
    	function bar() {
    		console.log("bar")
    	}
        console.log("foo")
    }
    
    foo();
    
    // 1. Global Execution Context - Lexical Environment
    
    GlobalEnvironment: {
    	EnvironmentRecord: {
        	a: 100,
            foo: <Function foo>,
        },
        [[OuterEnv]]: null
    };
    
    // 2. Functional Execution Context - 'foo' function Lexical Environment
    
    LexicalEnvironment: {
    	EnvironmentRecord: {
        	bar: <Function bar>
        },
        [[OuterEnv]]: GlobalLexicalEnvironment
    }

    환경 레코드의 구성

    EnvironmentRecord: {
    	// 선언적 환경 레코드
        DeclarativeEnvironmentRecord: {},
        // 객체 환경 레코드
        ObjectEnvironmentRecord: {}
    }

    선언적 환경 레코드

    함수와 변수, catch 문의 식별자와 실행결과가 저장되는 영역

    객체 환경 레코드

    • 실행 문맥 외부에 별도로 저장된 객체의 참조에서 데이터를 읽거나 쓴다.
    • Binding Object라는 객체와 연결된다. BInding Object의 프로퍼티 이름에 문자열 타입의 식별자 이름들을 바인딩한다.

    자바스크립트 프로그램의 실행 과정

    개요
    1. 전역 환경과 전역 객체의 생성
    2. 프로그램 평가와 전역 변수 => 생성 단계 (Creation Phase) 와 비슷하다.
    3. 프로그램의 실행과 실행 컨텍스트 => 실행 단계 (Execution Phase) 와 비슷하다.

    1.  전역 환경과 전역 객체의 생성

    웹 브라우저에 내장된 자바스크립트 인터프리터는 새로운 웹 페이지를 읽어 들인 후에 전역 환경을 생성한다. 그리고 전역 객체를 생성한 다음, 전역 환경의 객체 환경 레코드에 전역 객체의 참조를 대입한다.

    // 전역 환경
    GlobalEnvironment: {
    	ObjectEnvironmentRecord: {
        	bindObject: window
        },
        OuterLexicalEnvironmentReference: null
    }
    
    // 전역 실행 문맥
    GlobalExecutionContext: {
        LexicalEnvironment: GlobalEnvironment,
        ThisBindin: window
    }

    웹 브라우저의 자바스크립트 실행 환경에서는 Window 객체가 전역 객체이므로 객체 환경 레코드의 bindObject 프로퍼티에는 전역 객체 window의 참조가 할당된다. 이로 인해 전역 환경의 변수와 함수를 Window 안에서 검색하게 된다. 또한 전역 환경의 외부에는 다른 렉시컬 환경이 없으므로 외부 렉시컬 환경 참조에는 null을 할당한다. 그리고 전역 실행 문맥의 디스 바인딩 컴포넌트에도 Window의 참조가 할당되어 전역 실행 문맥의 this가 window를 가리키게 되고, 전역 실행 문맥의 프로퍼티를 디스 바인딩 컴포넌트 안에서 검색하게 된다.

     

    2. 프로그램의 평가와 전역 변수

    자바스크립트 엔진은 전역 코드를 평가할 때,
    전역 변수(최상위 레벨에 var) 문을 전역 환경의 환경 레코드(객체 환경 레코드)에 프로퍼티로 기록한다.
    이 프로퍼티의 이름은 식별자 이름이 되고, 프로퍼티 값은 undefined가 된다.
    앞서 설명한 내용에서 중요한 개념이 나온다. 이후 글에서 '호이스팅(hoisting)'에서 더 자세히 설명한다
    1. 선언(Declaration): 메모리 공간을 확보하고 식별자와 연결하고 기록하는 것
        => 전역 변수(var)를 전역 환경의 환경 레코드에 프로퍼티로 기록한다.
    2. 초기화(Initialization): 이 프로퍼티의 값을 undefined로 바인딩 하는 것
        => 프로퍼티의 이름은 식별자 이름이 되고, 프로퍼티 값은 undefined가 된다.

     

    함수의 경우에는,
    최상위 레벨에 작성된 함수 선언문을 함수 객체로 생성해서 전역 환경의 환경 레코드(객체 환경 레코드)에
    프로퍼티로 기록한다.

     

    함수 안에 선언된 지역 변수와 중첩 함수의 참조는 그 함수가 속한 시행 환경의 환경 레코드(선언적 환경 레코드)의 프로퍼티이다.

    이처럼 자바스크립트의 모든 변수를 객체의 프로퍼티로 간주하면 쉽게 이해할 수 있다.

     

    3. 프로그램의 실행과 실행 컨텍스트

     실행 컨텍스트는 스택이라는 구조로 관리된다. 스택이란 일종의 자료구조로 데이터를 아래에서부터 쌓아 올려서 마지막으로 추가한 데이터를 먼저 꺼내는 후입선출(LIFO, Last In First Out) 방식으로 관리된다. 스택의 가장 윗부분에 데이터를 쌓는 행위를 'push'라고 하고, 스택의 가장 윗부분에서 데이터를 빼내는 행위를 pop이라고 한다.

     실행 컨텍스트는 프로그램 실행 중에 스택에 push되어 실행된다. 가장 먼저 실행하는 코드는 전역 코드이며 이 때문에 스택의 맨 아랫부분에는 전역 실행 컨텍스트(Gloabal Execution Context)가 자리잡고 있다. 이후 실행되는 실행 컨텍스트를 스택에 push한다. 그리고 함수의 작업을 끝내고 함수를 호출한 부분으로 제어권이 돌아오면 스택에서 pop한다. 그림으로 표현하자면 아래와 같다.

    모던 자바스크립트 입문 내용

    실행 컨텍스트 스택을 호출 스택이라는 이름으로 부른다.

     

     

     

     

    위 내용은 모던 자바스크립트 입문(이소 히로소) 내용을 기반으로, 다른 레퍼런스들을 참고하여 필자의 생각을 가미하여 정리한 내용입니다.

     

     

    [참고]

    https://iamsjy17.github.io/javascript/2019/06/10/js33_execution_context.html

     

    Songlog

    Javascript, Typescript, Angular, React, RxJS, etc.

    iamsjy17.github.io

     

     

Designed by Tistory.