2025년 01월 14일

9

Execution Context (실행 컨텍스트) & 호이스팅

JavaScript

HS
Hyungseok Kwon
@hskwon5170

실행 컨텍스트란

실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체입니다.
자바스크립트는 동일한 환경에 있는 환경 정보들을 모은 실행 컨텍스트를 콜스택에 쌓아올린 후 실행하여 코드의 환경과 순서를 보장할 수 있게 됩니다.

스택의 경우 FILO (First In, Last Out) 의 구조이기때문에 순서를 보장, 콜스택 내부에 쌓인 실행 컨텍스트의 정보를 통해 환경을 보장할 수 있는 것입니다.
여기에서 환경이란 전역공간이 될 수 있고, 함수 내부의 환경이 될 수 있습니다.


1var temp = 'temp'; 2 3function 2 () { 4 console.log('안녕하세요'); 5} 6 7function 1 () { 8 2(); 9} 10 111();

image


NOTE

처음 자바스크립트의 코드를 실행하는 순간, Step 1처럼 전역 컨텍스트가 콜스택에 담깁니다.
부연하자면 함수가 실행됨에 따라 실행한 함수의 환경 정보들을 수집 -> 실행 컨텍스트를 생성 -> 콜스택에 담깁니다.
함수가 종료된 후 실행 컨텍스트가 콜스택에서 제거됩니다. 만약 모든 함수가 실행되고, 전역 공간에 실행할 코드가 남아있지 않다면 콜스택에서 전역 컨텍스트 또한 제거되며 콜스택에 아무것도 남지 않은 상태로 종료됩니다.

  • (1) 콜스택에는 전역 컨텍스트를 제외하고 다른 컨텍스트가 없기 때문에 전역 컨텍스트와 관련된 코드를 진행합니다.

  • (2) 전역 컨텍스트와 관련된 코드를 진행 중 func 1을 실행하였기에 func 1의 환경 정보들을 수집하여 func 1 실행 컨텍스트를 생성, 콜스택에 담습니다.
    콜스택 최상단에 func 1 실행 컨텍스트가 있기에 기존의 전역 컨텍스트와 관련된 코드의 실행을 일시적으로 중단한 후 func 1 실행 컨텍스트의 코드를 실행합니다.

  • (3) func 1 함수 내부에서 func 2 함수를 실행하였기에 func 2 함수의 환경 정보들을 수집, 실행 컨텍스트를 생성, 콜스택에 담습니다. 이전과 동일하게 콜스택의 최상단에 func 2 실행 컨텍스트가 있기때문에 기존 func 1 실행 컨텍스트와 관련된 코드의 실행을 일시 중단 합니다.

  • (4) func 2 함수가 종료된 후 func 2 실행 컨텍스트가 콜스택에서 제거됩니다. 제거 후 콜스택 최상단에는 func 1 실행 컨텍스트가 있기 때문에 이전에 중단된 지점부터 코드 진행이 재개됩니다.

  • (5) func 1 함수 또한 종료된 후 실행 컨텍스트가 콜스택에서 제거됩니다.

  • 이후에는 전역 공간에 실행할 코드가 남아있지 않다면 콜스택에서 전역 컨텍스트 또한 제거되며 콜스택에 아무것도 남지 않은 상태로 종료됩니다.



실행 컨텍스트는 어떤 구조로 이루어져 있을까

image
실행 컨텍스트는 크게 **Lexical Environment, Variable Environment, This Binding**세 가지 정보를 유지합니다.

1. Lexical Environment (렉시컬 환경)

렉시컬 환경은 자바스크립트의 스코프와 식별자 바인딩을 관리하는 구조입니다.

  • 1-1.Environment Record (환경레코드)
    • 해당 스코프에 포함된 식별자(변수, 함수 등)를 등록하고, 등록된 식별자에 대한 값을 관리하는 저장소
    • 실제 변수 이름과 값(또는 메모리 주소)이 기록됨
    • 예) 함수 내부에서 선언된 변수, 매개변수, 내부 함수 선언 등
  • 1-2. Outer Lexical Environment Reference (외부 렉시컬 환경에 대한 참조)
    • 상위 스코프(외부 환경)에 대한 참조를 의미합니다.
    • 현재 실행 컨텍스트를 생성한 소스코드를 둘러싸는 상위 스코프의 Lexical Environment를 가리켜, 변수 참조가 필요한 경우를 해결합니다.
    • 예) 함수 중첩 구조에서 내부 함수가 외부 함수의 변수를 참조할 때, 이 참조를 따라 상위 스코프를 탐색합니다.

1const hello = 'hello'; 2 3const animal = () => { 4 const hyuji = { 5 color: 'white', 6 breed: 'bichon frise' 7 } 8 9 const mari = { 10 color: 'brown', 11 breed: 'welsh corgi' 12 } 13 14 console.log(hello) 15 console.log(hyuji) 16 console.log(mari) 17} 18 19animal() // 'hello', {color: 'white', breed: 'bichon frise'}, {color: 'brown', breed: 'welsh corgi'} 20console.log(hyuji) // ReferenceError: hyuji is not defined 21console.log(mari) // ReferenceError: mari is not defined

위 코드에서 animal 함수 내부에서는 외부에 선언된 hello 변수와 내부에 선언된 hyuji, mari 변수에 접근할 수 있습니다. 반면, 함수 외부에서는 hyujimari에 접근할 수 없어 ReferenceError가 발생합니다. 이는 스코프 규칙에 따른 정상적인 동작입니다.

outerEnvironmentReference 란 현재 호출된 함수가 선언될 당시Lexical Environment를 참조합니다.
여기에서 선언될 당시가 중요한데, animal 함수가 선언될 당시의 outerEnvironmentReference는 글로벌 실행 컨텍스트의 LexicalEnvironment를 참조하고 있으며, 해당 환경의 environmentRecord에 hello와 같은 변수의 정보들이 기록되어 있습니다.

그렇기에 함수 내부에서는 outerEnvironmentReference 를 통해 상위 컨텍스트의 LexicalEnvironment에 접근하여 envronmentRecord 에서 변수인 hello를 사용할 수 있게 되는 것입니다.

더불어 outerEnvironmentReference는 오직 자신이 선언될 당시의 LexicalEnvironment를 참조하기에 순차적으로만 접근이 가능하며, 여러 스코프에서 동일한 식별자를 생성하였다 하더라도 가장 먼저 발견된 식별자만 접근이 가능합니다.

image

1const message = 'hello'; 2const hihi = 'hihi'; 3 4const sayHi = () => { 5 const message = 'hi'; 6 console.log(message); 7 console.log(hihi); 8 console.log(hello) 9}

message 변수
현재 컨텍스트의 LexicalEnvironment => environmentRecord에 message 라는 식별자가 있고, outerEnvironmentReference => LexicalEnvironment => environmentRecord에도 message 식별자가 있지만,
가장 먼저 발견된 식별자에 바인딩된 값인 'hi'가 출력, ㅇ라게 모르게 전역 컨텍스트에서 선언한 message의 경우 변수 은닉화가 됨

hihi 변수
현재 컨텍스트의 LexicalEnvironment => environmentReecord에 hihi라는 식별자가 존재하므로 outerEnvironmentReference를 참조하여 전역 컨텍스트의 LexicalEnvironment를 참조, environmentRecord의 hihi 식별자에 접근하여 hihi 출력

hello 변수
현재 컨텍스트의 LexicalEnvironment => environmentRecord와 outerEnvironmentReference => LexicalEnvironment => environmentRecord가 없으므로 즉, 콜스택의 최하단에 위치한 전역 실행 컨텍스트에도 없다면 undefined를 출력

NOTE

정리하자면, Lexical Environment는 현재 스코프의 식별자 정보(Environment Record)와 상위 스코프에 대한 참조(Outer Reference)로 구성되어 있습니다. 이를 통해 자바스크립트는 스코프 체인을 따라 변수를 검색합니다.
outerEnvironmentRefrernce 란 해당 함수가 선언된 위치의 LexicalEnvironment를 참조하며, 변수에 접근을 한다면 해당 LexicalEnvironment에서 발견된다면 사용, 찾지 못할 경우 다시 outerEnvironmentReference를 참조하여 탐색하는 과정을 반복합니다. 이러한 과정을 스코프 체인이라고 하며 outerEnvironmentReference는 스코프체인을 가능케 합니다.




2. Variable Environment (변수 환경)

Variable Environment 역시 Environment RecordOuter Lexical Environment Reference를 갖지만 처음 실행 컨텍스트가 생성되는 순간의 스냅샷 형태로 var 변수, 함수 선언 등을 담고 있습니다.

  • Lexical Environment와 구조는 유사하지만, 자바스크립트 최신 사양에서는 let, const 키워드가 렉시컬 환경 기반으로 처리되는 반면, var는 변수 환경에서 다뤄진다고 구분하기도 합니다.
    • ES6 이후 블록 레벨 스코프(let, const) 도입으로 Variable Environment와 Lexical Environment가 분리되어 동작합니다.

정리하자면, var는 Variable Environment, let/const는 Lexical Environment에 의해 관리된다고 이해해두면 좋습니다.




3. This Binding

  • 실행 컨텍스트 생성 시 결정되는 this 참조 정보.
  • 함수 호출 방식(일반, 메서드, 생성자 등)에 따라 this가 달라짐.

결국 실행 컨텍스트는 “어떤 스코프에서 어떤 식별자가 어떤 메모리를 참조하고, this는 어디를 가리키는지”를 결정하는 핵심 메커니즘입니다. 자바스크립트는 이러한 구조를 통해 스코프 체인을 형성하고, 변수 검색클로저 동작 방식을 구현합니다.


호이스팅이란


1번 코드

1var apple = '사과'; 2 3console.log(apple); // apple

2번 코드

1console.log(apple) // undefined 2 3var apple = '사과'

2번 코드에서 선언하기도 전에 값을 호출했는데 Reference Error가 발생하지 않고 undefined (할당 되지 않음) 만 출력됩니다.
이것은 자바스크립트의 호이스팅이라는 현상과 관련이 있습니다.


NOTE

호이스팅이란?
자바스크립트 엔진이 실행 컨텍스트를 구성할때 Environment Record에 식별자 정보를 수집합니다. 이러한 과정을 통해 엔진은 함수를 실행하기도 전에 해당 컨텍스트 내부의 변수명을 이미 알고 있습니다.
이렇기에 식별자들을 코드의 최상단으로 끌어올렸다 라는 호이스팅이라는 개념이 생겨납니다. 물리적으로 끌어올린 것이 아닌, 실행 컨텍스트 관점에서는 이미 식별자들의 정보를 알고 있으니 식별자 정보를 수집하는 과정을 이해하기 쉬운 방법으로 나타낸 추상화한 가상 개념입니다.

정리하자면, Lexical Environment와 Environment Record의 경우 해당 컨텍스트 환경에 필요한 식별자와 식별자의 값이 기록되며, 함수 실행 시 실행 컨텍스트가 생성되므로 (함수 실행보다 Environment Record 수집이 먼저 되므로) 변수와 같은 식별자를 끌어올리는 것과 같다 라는 개념의 호이스팅이 생겨났습니다.