치춘짱베리굿나이스

실행 컨텍스트 본문

Javascript + Typescript/이론과 문법

실행 컨텍스트

치춘 2023. 8. 23. 22:46

실행 컨텍스트

호이스팅을 알아보기 전에 실행 컨텍스트에 관해서 짚고 넘어갈 필요가 있다

호이스팅이랑 같이 적으려다가 실행 컨텍스트 쪽 분량이 너무 방대해지는 바람에 호이스팅이랑 분리함…

설명

자바스크립트에서 사용되는 객체로, (자바스크립트는 진짜 모든 것이 객체 같다…) 실행할 코드에 제공할 변수, 함수 등의 정보들을 모아놓는 공간이라고 말할 수 있겠다

쉽게 말하자면 코드의 실행 환경을 객체로 저장해둔 것이라고 생각하면 좋다

이 실행 컨텍스트는 콜 스택에 적재되어 함수가 순서대로 실행될 수 있도록 한다

콜 스택 (호출 스택)

https://blog.chichoon.com/701

자세한 것은 이 포스팅을 참조하자

코드가 실행될 때마다 실행 컨텍스트 (프레임) 가 쌓이는 공간이다

말 그대로 스택이라 LIFO 방식으로 동작하며, 함수가 실행될 때 후술할 함수 실행 컨텍스트가 생성되어 스택에 push되고, 함수의 코드 실행이 전부 끝나면 스택에서 pop된다

콜 스택의 용량에 비해 너무 많은 실행 컨텍스트가 적재될 경우 (예시: 무한 재귀) 스택 오버플로우가 발생해 프로그램이 터질 수 있다

실행 컨텍스트 종류

실행 컨텍스트는 저마다의 생성 시점에 생성되어 콜 스택에 push된다

전역 컨텍스트

스크립트가 실행될 때 가장 먼저 생성되는 실행 컨텍스트로, 전역 정보들을 총체적으로 관리하는 환경이다

스크립트가 실행될 때 (프로그램이 실행될 때) 단 한 개만 생성되며, 함수 밖에 있는 정보들은 전부 전역 컨텍스트에 모인다

브라우저에서는 window 객체, Node.js에서는 global 객체가 전역 컨텍스트가 된다

함수 컨텍스트

함수가 호출 (실행) 될 때마다 하나씩 생성되는 컨텍스트이다

생성되는 컨텍스트들의 대부분을 차지한다 (전역 컨텍스트는 단 하나, eval 컨텍스트는 잘 사용되지 않으므로…)

eval 컨텍스트

eval 함수를 실행할 때마다 생성되는 컨텍스트이다

eval 자체가 문자열로 된 코드를 강제로 실행시키는 매우 위험한 함수이기 때문에 잘 사용되지 않는다

실행 컨텍스트가 담고 있는 정보들

글씨 완전 못씀

VariableEnvironment

현재 컨텍스트 내부의 식별자 (변수) 들에 대한 정보 (environmentRecord) 와, 외부 환경에 대한 정보 (outerEnvironmentReference) 가 포함되어 있다

VariableEnvironment 는 초기값 (최초 실행 시에 저장되는 값) 들을 담고 있으며, 이를 그대로 복사해서 하단의 LexicalEnvironment를 만든다고 한다

VariableEnvironment 가 저장하는 값은 스냅샷 개념으로, 초기 상태만 담고 있다는 것을 명심하자

var으로 선언된 변수는 이곳에 저장이 된다

 

environmentRecord

  • 함수와 변수의 정의가 저장된 곳이라고 생각하면 된다
  • 현재 컨텍스트와 관련된 식별자들 (매개변수, 함수 그 자체, 함수 내부의 식별자) 을 전부 수집하여 넣어둔다
  • environentRecord는 총 3종류가 있는데,
    • Declaratie Environment Record : var, const, let, class, module, import, function 등의 선언적인 식별자들에 관한 정보가 담겨 있다
      • 얘가 또 Function Environment Record, Module Environment Record 두 종류로 나뉜다
    • Object Environment Record : with 키워드와 함께 생성되는 레코드
    • Global Environment Record : 모든 자바스크립트 코드들이 공유하는 최상위 스코프를 가리킨다
  • 이 녀석이 호이스팅의 원인이 된다

outerEnvironmentReference

  • 상위 렉시컬 환경 (상위 스코프) 을 가리킨다
  • 상위 렉시컬 환경이라는 것은, 본 컨텍스트를 생성한 함수의 외부 환경, 즉 해당 함수가 선언된 시점의 함수 바깥 스코프를 말한다
  • 하단에 설명할 LexicalEnvironment를 참조하는 포인터라고 생각하자
  • 이 녀석이 스코프 체이닝의 원인이 된다
    • 만약 현재 스코프에서 값을 못 찾았을 경우, outerEnvironmentReference 를 타고 바깥 스코프로 넘어가 값을 계속 찾는 현상

ThisBinding

  • 함수 컨텍스트의 경우, 함수가 바인딩된 this 객체를 가리키며, 동적으로 바인딩된다
  • 이는 call, bind, apply, 생성자 호출, 객체 내부 호출 등등… 경우에 따라 천차만별로 달라지는 것
  • 전역 컨텍스트의 경우, 전역 객체 (window / global) 를 가리킨다

LexicalEnvironment

VariableEnvironment로부터 복사해온 변수 정보들을 담으며, 변수나 함수 등에 변경사항이 생길 경우 실시간으로 반영되는 영역이다

따라서 VariableEnvironment와 구성 요소는 같으나, 실시간으로 값이 변동된다는 차이점이 있다

클로저를 공부할 때 많이 나오는 렉시컬 환경의 정체가 바로 이 녀석이다

let, const로 선언한 변수는 이곳에 저장이 된다

 

environmentRecord

outerEnvironmentReference

ThisBinding

실행 컨텍스트 생성 및 작동 과정

실행 컨텍스트 생성 순서

let a = 1;

function foo() {
    let b = 2;
    function bar() {
        let c = 3;
        console.log(a + b);
    }
    bar();
}
foo();

function baz() {
    let d = 4;
}
baz();

실행 컨텍스트는 콜 스택에 하나씩 쌓이며, 스택이므로 LIFO 구조로 함수가 실행되고 컨텍스트가 제거된다

함수 컨텍스트는 함수 호출을 발견할 때마다 생성되며, 콜 스택의 최상단에 푸시된다

  1. script 요소를 처음으로 찾았을 때, 전역 컨텍스트를 생성하여 푸시한다
  2. 전역 컨텍스트의 코드를 실행하다가 foo() 함수 호출을 발견한다
    • 전역 컨텍스트에서의 코드 실행을 일시정지하고 foo 함수에 대한 실행 컨텍스트를 생성하여 스택에 푸시한다
  3. foo() 컨텍스트의 코드를 실행하다가 bar() 함수 호출을 발견한다
    • foo 컨텍스트에서의 코드 실행을 일시정지하고 bar 함수에 대한 실행 컨텍스트를 생성하여 스택에 푸시한다
  4. bar() 컨텍스트의 코드를 실행하고, 함수가 종료되면 bar 컨텍스트를 스택에서 pop한다
  5. foo() 컨텍스트의 코드를 마저 실행하고, 함수가 종료되면 foo 컨텍스트를 스택에서 pop한다
  6. 전역 컨텍스트의 코드를 마저 실행하다가 baz() 함수 호출을 발견한다
    • 전역 컨텍스트의 코드 실행을 다시 일시정지하고, baz 함수에 대한 실행 컨텍스트를 생성하여 스택에 푸시한다
  7. baz 컨텍스트이 코드를 실행하고, 함수가 종료되면 baz 컨텍스트를 스택에서 pop한다
  8. 전역 컨텍스트의 코드를 마저 실행 후, 모든 코드의 실행이 종료되면 전역 컨텍스트가 콜 스택에서 pop된다

실행 컨텍스트 생성 단계 - Creation Phase

실행 컨텍스트가 생성되는 시점이다

  • 브라우저가 script 태그를 읽어들여 실행하는 시점이 전역 컨텍스트의 Creation Phase가 된다
  • 함수가 호출되어 불러와지는 시점이 함수 컨텍스트의 Creation Phase가 된다

실행 컨텍스트가 생성된다는 것은, 곧 VariableEnvironmentLexicalEnvironment 가 현재 함수 기준으로 생성된다는 뜻이다

이 시점에서 변수와 함수 등의 식별자가 저장되므로, 우리가 아는 호이스팅이 발생하는 것

이때 변수와 함수 등의 식별자만 저장되고 값의 전달 (할당) 은 아래의 Execution Phase에서 일어나므로 호이스팅된 변수들은 undefined 상태가 된다

실행 컨텍스트 실행 단계 - Execution Phase

위의 Creation Phase를 거쳐 모든 변수에 대한 값 할당이 완료되면, Execution Phase로 넘어가 코드들이 실행되기 시작한다

Creation Phase에서 할당되지 않은 변수 값들은 Execution Phase에서 비로소 할당이 되기 시작하여 값을 갖게 된다

코드는 위에서 아래로 읽힌다고 한다

실행 컨텍스트와 관련된 현상

environmentRecord와 호이스팅

우리는 위에서 environmentRecord가 Creation Phase 시점에서 변수와 함수 등등에 대한 정보를 수집한다는 것을 이제 알고 있다

이 때문에 자바스크립트는 코드 실행 전 (실행 컨텍스트 Execution Phase 시점 전) 변수와 함수의 이름들을 전부 알고 있는 것

그리고 이 현상이 바로 호이스팅이다… 실행 컨텍스트가 생성되면서 변수와 함수의 선언 정보 (할당은 아님) 가 컨텍스트에 미리 저장이 되기 때문에, 마치 식별자들의 선언부가 코드의 최상단으로 끌어올려진 것처럼 동작하는 것이다

실제로 코드가 끌어올려지는 것은 아니지만 코드를 해석할 때는 그렇게 받아들이는 것이 이해가 쉽기 때문에 “선언부가 끌어올려진다” 라고 많이들 말하는 것이다

outerEnvironmentReference와 스코프 체인

우리의 함수 실행 컨텍스트는 LexicalEnvironment와 VariableEnvironment 내부에 outerEnvironmentReference를 통해 함수가 선언될 시점의 외부 실행 컨텍스트를 가지고 있다

 

function foo() {
    const a = 1;
    function bar() {
        const b = 2;
        console.log(a + b); // 3
    }
    bar();
}

bar() 가 호출되면서 실행 컨텍스트가 생성될 시점의 LexicalEnvironment는 foo() 가 된다

outerEnvironmentReference는 실행 컨텍스트가 생성될 때의 LexicalEnvironment를 참조하므로, bar() 의 outerEnvironmentReference는 foo() 의 LexicalEnvironment를 참조한다는 것

따라서 bar() 함수에서 변수 a를 찾는 코드에 도달하면, bar() 내부에는 a가 존재하지 않기 때문에 outerEnvironmentReference를 타고 외부 컨텍스트로 넘어가서 foo() 환경에서 a를 탐색하여 사용하게 된다

 

그래프로 표현하자면 요렇게 연결되어있달까,,

outerEnvironmentReference가 바깥쪽 함수 컨텍스트의 LexicalEnvironment를 가리키며, 따라서 외부의 변수를 참조할 수 있다고 생각하면 된다

이 특성 때문에 함수는 외부 컨텍스트의 변수를 참조할 수 있지만, 자신의 내부에 있는 함수 (및 컨텍스트) 의 변수는 접근하지 못한다

결론

호이스팅이 쏘아올린 작은 공… 근데 호이스팅은 원리 자체가 실행 컨텍스트를 모르면 이해가 아예 안 되는 수준이라 이렇게 정리하게 되었다

실행 컨텍스트 한 번 정리해 두면 클로저나 호이스팅, 렉시컬 환경, 스코프 체이닝 등 자바스크립트에서의 신기빵기한 현상들 (?) 에 대해 이해가 쏙쏙 되니 역시 한번 쯤 짚고 넘어가는 게 좋을듯싶다


참고 자료

https://junilhwang.github.io/TIL/Javascript/Domain/Execution-Context/#_2-실행-컨텍스트-구성

https://velog.io/@shroad1802/environment-record

https://gist.github.com/snaag/de92e29268429ada9da686125ac937e1

https://gamguma.dev/post/2022/04/js_execution_context

'Javascript + Typescript > 이론과 문법' 카테고리의 다른 글

비동기와 콜백 함수 ver. 2023  (0) 2023.09.19
호이스팅  (0) 2023.08.24
any, unknown, never  (0) 2023.08.21
var, let, const 차이점  (0) 2023.08.17
문자열의 특정 문자 변경하기  (0) 2023.08.08
Comments