실행 컨텍스트

Created
April 7, 2024
Tags
JavaScript

실행 컨텍스트란(Execution Context)

JavaScript 엔진은 코드를 해석하고 실행하는 복잡한 특성을 관리하기 위해 실행 컨텍스트를 사용한다. 코드를 실행하기 위해서는 변수, 함수, 스코프, 식별자, 코드 실행 순서 등의 정보를 알아야 하며 관리도 필요하다. 실행 컨텍스트는 이러한 것들을 관리한다. 즉, JavaScript 코드가 실행되는 환경이다. 또한, 코드를 평가하고 실행에 필요한 정보들을 모아놓은 객체이다.

호이스팅에 대해 학습하는 동안 “실행 컨텍스트” 라는 용어가 여러 번 등장했다. 잠깐 들여다봐도 쉬운 개념이 아니었다. 실행 컨텍스트를 바르게 이해하면 코드의 실행 흐름, 호이스팅이 발생하는 이유, 식별자와 식별자 바인딩을 관리하는 방식 등 JavaScript의 핵심 원리와 여러 가지 특징들을 파악하는 데 매 우 도움이 된다.

image

실행 컨텍스트 종류

JavaScript 엔진은 실행 가능한 코드를 읽기 시작하면 그 소스 코드를 평가한다. JavaScript에서 실행 가능한 코드란 뭘까?

  1. 전역 코드
  2. 함수 코드
  3. eval 함수 코드

그리고 위 소스 코드 타입에 따라 각각 다른 실행 컨텍스트를 생성한다. 이렇게 타입으로 구분하는 이유는 소스 코드 타입에 따라 실행 컨텍스트를 생성하는 과정과 관리 내용이 다르기 때문이다. 그럼 실행 컨텍스트 종류에 대해 알아보자!

1. 전역 실행 컨텍스트(Global Execution Context)

전역 코드의 실행은 JavaScript 파일이 처음 로드되면서 시작된다. 그리고 먼저 전역 객체를 생성한다. 브라우저 환경에서는 이를 window 객체, Node.js 환경에서는 global 객체라 부른다.

전역 객체가 생성되면 전역 코드가 평가되며 이후 전역 실행 컨텍스트가 생성된다. 이 실행 컨텍스트는 전역 스코프를 나타내며, 전역 객체가 이 스코프의 변수 및 함수에 대한 참조를 가지게 된다.

2. 함수 실행 컨텍스트(Function Execution Context)

JavaScript 엔진은 함수가 호출될 때마다 함수 실행 컨텍스트를 생성하며, 모든 함수는 호출되는 시점에 자신만의 새로운 함수 실행 컨텍스트를 가진다.

3. eval 함수 실행 컨텍스트(eval Function Execution Context)

eval 함수는 문자열로 표현된 JavaScript 코드를 실행하는 함수이다. eval 함수를 호출하면 eval 함수 실행 컨텍스트가 생성된다. 하지만 eval 함수가 가지는 취약점 때문에 사용하지 않을 것을 권장하고 있다. (MDN eval())

🔍
JavaScript 엔진은 처음 코드를 실행할 때 단 한 번 Global Execution Context를 생성하며 함수를 호출할 때 마다 함수를 위한 Execution context를 생성한다.

JavaScript 엔진은 어떻게 동작하는가?

💡 JavaScript 엔진은 소스 코드를 평가실행(Runtime) 과정으로 나눠서 처리하여 코드를 해석하고 실행한다. 이어서 학습할 내용들을 위해 미리 이해하고 있자.

평가 과정

JavaScript 엔진이 실행 컨텍스트를 생성하고, 변수와 함수의 선언문만 먼저 확인해서 식별자를 키로 실행 컨텍스트가 관리하는 *렉시컬 환경*환경 레코드에 등록한다. 코드의 실행 순서는 실행 컨텍스트 스택을 통해 관리한다.

다시 말해 코드 실행 전에 코드를 먼저 평가하는 것인데, 실행 컨텍스트 스택을 통해 코드를 읽고 컴파일 과정을 거치면서 변수, 함수 등의 선언문을 파악하되 코드를 실행하지는 않는다.

실행 과정

평가 과정이 끝나면 선언문을 제외한 소스 코드가 순차적으로 실행된다. 이를 런타임(Runtime)이라고 한다. 소스 코드 실행에 필요한 변수나 함수의 참조는 실행 컨텍스트가 관리하는 환경 레코드에서 검색하여 사용한다.

🔍
코드의 실행 순서는 실행 컨텍스트 스택으로 관리하고, 식별자와 스코프는 실행 컨텍스트의 렉시컬 환경으로 관리한다.

이렇게 생성된 실행 컨텍스트들은 실행 컨텍스트 스택에 쌓인다. 과정을 알아보자.

실행 컨텍스트 스택

콜 스택(Call stack)이라 불리는 자료구조에 생성된 실행 컨텍스트가 쌓인다. 스택 자료구조는 LIFO(Last In, First Out)을 따르고 있어 코드 실행 순서를 보장한다. 콜 스택 내부에 쌓인 실행 컨텍스트의 정보를 통해 JavaScript 엔진은 어떤 함수가 실행 중인지, 호출되었는지 등을 추적하며 코드의 환경과 순서를 보장한다.

콜 스택 동작

JavaScript 엔진이 처음 코드를 실행하면 전역 실행 컨텍스트를 생성하고 이를 콜 스택에 추가(push)한다. 그 후 JavaScript 엔진이 함수를 호출할 때마다 그 함수를 위한 함수 실행 컨텍스트를 생성하고 이를 콜 스택에 추가한다. JavaScript 엔진은 콜 스택의 최상단에 위치한 함수를 실행하며 함수가 종료되면 콜 스택에서 제거(pop)하고 다음 최상단에 위치한 함수로 이동한다.

코드와 그림을 통해 실행 컨텍스트가 스택에 쌓이는 과정을 알아보자.

image
image
Step
Description
0
코드 실행 전
콜 스택은 전부 비워진 상태로 시작한다.
1
전역 코드 평가 및 실행
- 전역 코드를 평가해서 전역 실행 컨텍스트를 생성하고 콜 스택에 추가한다. - 전역 코드에는 변수 x와 함수 foo가 있고, 이를 전역 실행 컨텍스트에 등록한다. - 평가가 끝나면 전역 코드를 실행하고 전역 변수 x에 1을 할당, 전역 함수인 foo를 호출한다.
2
foo 함수 코드 평가 및 실행
- foo 함수가 호출되면 foo 함수 실행 컨텍스트를 생성하고 콜 스택에 추가한다. 이때 전역 코드의 실행은 잠시 중단되고, 코드 제어권이 foo 함수로 넘어간다. - 지역 변수 y와 지역 함수 barfoo 함수 실행 컨텍스트에 등록된다. - foo 함수의 평가가 끝났으면 foo 함수를 실행해서 bar 함수 실행 코드를 만나게 된다.
3
bar 함수 코드 평가 및 실행
- bar 함수가 호출되면 bar 함수 실행 컨텍스트를 생성하고 콜 스택에 추가한다. 이때 foo 함수 실행은 잠시 중단되고, 제어권이 bar 함수 내부로 넘어간다. - bar 함수 내부의 코드를 평가하고 지역 변수 zbar 함수 실행 컨텍스트에 등록된다. - bar 함수를 실행한다. 변수 z에 3이 할당되고 console.log 메서드를 호출한 이후 bar 함수는 종료된다.
4
foo 함수 코드로 복귀
- bar 함수가 종료된 후 콜 스택에서 bar 함수 실행 컨텍스트를 제거한다. - 코드의 제어권은 다시 foo 함수로 넘어간다. - 그리고 foo 함수 또한 더 이상 실행할 코드가 없으므로 종료된다.
5
전역 코드로 복귀
- foo 함수가 종료된 후 콜 스택에서 foo 함수 실행 컨텍스트를 제거한다. - 코드의 제어권은 전역 코드로 넘어간다. - 전역 코드에서도 더 이상 실행할 코드가 없으므로 종료된다.
6
코드 실행 완료
- 전역 코드가 종료된 후 콜 스택에서 전역 실행 컨텍스트를 제거한다. 콜 스택은 모두 비워졌다.

JavaScript 엔진이 실행 컨텍스트 스택을 통해서 어떻게 실행 컨텍스트를 관리하는지 살펴보았다. 그렇다면 실행 컨텍스트 내부에는 어떤 정보가 담길까? 어떤 구조로 되어 있을까? 이제 실행 컨텍스트 구조에 대해 알아볼 것이다. 또한 코드를 평가하는 과정과 코드 실행에 따른 실행 컨텍스트의 변화에 대해서도 알아보자.

실행 컨텍스트 구조

image

사실 학습하는 과정에서 혼돈의 카오스였다. 실행 컨텍스트 컴포넌트 구조는 ECMAScript 버전마다 바뀌었는데 최신 블로그들을 보면 여러 버전의 ES가 섞여 있는 글들이 많았다. 나는 ES2022(ECMAScript 2022) 버전 기준으로 설명하려고 한다. (참고: https://velog.io/@kdeun1/ECMAScript-버전별-Execution-Context-정리)

ES5 이전 실행 컨텍스트 구조

ExecutionContext: {
	변수 객체,
	스코프 체인,
	this 바인딩
}
  1. 변수 객체(Variable Object)
  2. 변수, 매개변수(parameters)와 인수(arguments) 정보, 함수 선언의 정보들을 가진 객체이다. 즉, 코드가 실행될 때 필요한 각종 정보들을 담아두는 곳이다.

  3. 스코프 체인(Scope Chain)
  4. 스코프는 현재 실행 중인 컨텍스트의 유효한 범위를 말한다. 실행 컨텍스트에서 현재 스코프의 변수 객체뿐만 아니라 외부 스코프의 변수 객체에도 접근(검색)할 수 있도록 관계를 연결한 것이 스코프 체인이다.

    즉, 스코프 체인은 변수 및 함수 식별자를 찾을 때 현재 스코프부터 시작해 외부 스코프로 계속 거슬러 올라가며 검색하는 매커니즘이다. 이를 통해 함수 내부에서 외부 변수에 접근이 가능하다.

  5. this 바인딩
  6. this가 어떤 객체를 참조하고 있는지에 대한 정보를 관리한다.

ES5 이후 실행 컨텍스트 구조

ES5 이후 letconst 같은 블록 스코프 개념이 추가되면서 큼지막한 구조는 아래와 비슷하나 내부 컴포넌트가 조금씩 바뀌어 왔고, 현재 ES2022(ECMAScript 2022) 버전 구조는 아래와 같다. 이어서 자세하게 다루겠다.

ExecutionContext: {
	LexicalEnvironment: {
		EnvironmentRecord,
		OuterEnvironmentReference,
		ThisBinding,
	},
	VariableEnvironment: {
		EnvironmentRecord,
		OuterEnvironmentReference,
		ThisBinding,
	},
	PrivateEnvironment: {
		...
	}
}

Lexical Environment

Lexical Environment를 “📖 사전” 이라고 생각해보자. Lexical Environment변수와 함수에 대한 이름과 그에 해당하는 값(식별자)을 매핑해 놓은 일종의 사전이다. 우리는 이 사전을 통해 코드 실행 중에 필요한 변수와 함수를 찾고, 해당 값을 가져와서 사용할 수 있게 된다.

Lexical Environment는 실행 컨텍스트 내에서 변수와 함수가 어디에 선언되었는지에 따라 동적으로 변경된다. 즉, 스코프를 표현하며 Lexical Environment가 어디인지에 따라서 결정된다.

사전적으로 설명하면 키와 값을 갖는 객체 형태의 스코프(함수, 블록 스코프)를 생성하여 식별자를 키로 등록하고 식별자에 바인딩 된 값을 관리한다. 또한, 상위 스코프에 대한 참조를 기록하는 자료구조라고 하나보다 :)

식별자들이 매핑된다는 것이 무엇인지 쉽게 이해하기 위해 간단한 코드를 준비했다. 식별자는 변수 a, b와 함수 Example() 이렇게 3개가 있다. 이 식별자들은 Lexical Environment에 매핑되어 아래와 같은 모양이 만들어진다.

let a = 10;
let b = 20;

function Example() {
	...
}
LexicalEnvironment: {
	a: 10,
	b: 20,
	Example: <func>
}

위 모양은 이해를 쉽게 돕기 위해 간단하게 만든 것이고 사실 Lexical Environment는 언급했듯이 Environment Record, Outer Environment Reference, This Binding이라는 3가지 프로퍼티를 갖는다.

간단히 말하면, 우리가 코드에서 사용하는 변수와 함수 등이 환경 레코드(Environment Record)에 저장되고 관리된다. 자, 이쯤 되면 Lexical Environment와의 관계를 알겠는가? Lexical Environment는 코드의 스코프와 관련된 정보를 가지고 있으며, 그 중 환경 레코드는 실제로 값들을 저장하고 관리하는 역할이다.

Environment Record

Lexical Environment 내에 존재하는 변수와 함수 선언에 대한 정보를 관리하는 곳이다. 변수와 함수의 식별자를 등록하고, 식별자에 바인딩 된 값을 관리한다. var a = 10;이라는 코드가 있을 때 이 변수를 저장하고 필요할 때 꺼내쓰는 곳이라고 생각하면 된다. 변수에 접근하고 값을 할당하거나 함수를 호출할 때 사용된다.

중요한 점은 실행 컨텍스트가 생성될 때 해당 컨텍스트의 종류나 코드의 성격에 따라 다양한 환경 레코드가 사용된다는 것이다.

전역 환경 레코드(Global Environment Record)

전역 스코프에 해당하는 실행 컨텍스트의 환경 레코드이다. 무슨 뜻이냐면 전역 스코프에서 코드가 실행될 때 전역 실행 컨텍스트가 생성되지 않는가. 그 안에 Global Lexical Environment가 형성되고 이 환경에 전역 환경 레코드가 생성된다. 이 레코드는 전역 스코프에서 사용되며, 전역 변수, 전역 함수, 전역 객체와 관련된 정보를 저장하고 제공한다.

그 중 Global Lexical Environment에 형성된 전역 환경 레코드에는 객체 환경 레코드(Object Environment Record)선언적 환경 레코드(Declarative Environment Record)라는 두 가지 유형의 프로퍼티를 가지고 있다.

객체 환경 레코드(Object Environment Record)

객체 환경 레코드var로 선언된 전역 변수, 함수 선언문으로 정의된 전역 함수, 빌트인 전역 프로퍼티, 빌트인 전역 함수, 표준 빌트인 객체들을 등록하고 관리한다. 그리고 이때 BindingObject라는 개념이 등장한다.

빌트인 전역 프로퍼티와 함수, 그리고 표준 빌트인 객체

전역 객체와 BindingObject

  • 객체 환경 레코드BindingObject라는 프로퍼티를 가지고 있으며, 객체 환경 레코드에 등록된 식별자들은 BindingObject의 프로퍼티가 된다.
  • GlobalExecutionContext: {
    	GlobalLexicalEnvironment: {
    		GlobalEnvironmentRecord: {
    			ObjectEnvironmentRecord: {
    				BindingObject
    			}
    		}
    	}
    }
  • BindingObject 프로퍼티는 전역 객체(window 또는 global)를 참조하기 때문에 var로 선언한 변수나 전역 함수 등은 BindingObject를 통해서 전역 객체의 프로퍼티로 등록된다.
  • 따라서, var로 선언된 전역 변수와 함수 선언문으로 정의된 전역 함수, 객체 등은 window의 프로퍼티로 접근할 수 있는 것이다.
  • // 전역 변수 선언
    var globalVar = 10;
    
    // 전역 함수 선언
    function globalFunction() {
      console.log("Hello, global function!");
    }
    
    // 객체 선언
    var myObject = { key: "value" };
    
    // 객체 환경 레코드에 등록된 것들은 BindingObject의 프로퍼티가 됨
    // 전역 객체(window)에 등록됨
    console.log(window.globalVar);        // 10
    window.globalFunction();              // Hello, global function!
    console.log(window.myObject.key);     // value

선언적 환경 레코드(Declarative Environment Record)

선언적 환경 레코드let, const로 선언된 전역 변수를 등록하고 관리한다.

letconst로 선언된 변수는 선언과 초기화가 분리되어 진행된다. 평가 단계에서는 선언만이 이루어지며, 이때 변수는 식별자로 등록되어 *TDZ(Temporal Dead Zone)에 들어가게 된다. 이후 실행 단계에서 선언문을 만나면 값이 초기화된다. 따라서 이들은 초기화 이전까지 즉, 자신의 선언문이 실행되기 전까지 TDZ 상태에 있고, TDZ 상태의 변수에 접근하려고 하면 ReferenceError를 일으킨다.

반면에 var로 선언된 전역 변수는 선언과 초기화가 평가 단계에서 동시에 진행되므로 실행 단계에서 이미 식별자들의 정보를 알고 있다.

💡
환경 레코드와 호이스팅 눈치챘는가? 평가 단계에서 let, const로 선언된 변수는 어느 값도 가지고 있지 않고, var로 선언된 변수는 값이 초기화된다. JavaScript 엔진이 실행 컨텍스트를 생성할 때 환경 레코드에서 식별자의 정보를 수집한다. 따라서 엔진은 코드를 실행하기도 전에 해당 컨텍스트 내부의 식별자들을 이미 알고 있다. 이러한 매커니즘을 통해 호이스팅의 개념이 생겨난 것이다! 물리적으로 끌어올린 것이 아니라, 실행 컨텍스트 관점에서 이미 식별자들의 정보를 파악한다는 것을 의미한다.

함수 환경 레코드(Function Environment Record)

함수 실행 컨텍스트Lexical Environment Record는 함수 환경 레코드 하나의 프로퍼티를 가진다. 함수가 호출될 때마다 새로운 함수 환경 레코드가 생성된다고 이해하면 되겠다. 함수 환경 레코드는 함수의 스코프, 매개변수, 함수 내에서 선언된 변수 및 중첩된 함수 등의 식별자를 등록하고 관리한다.

Outer Environment Reference

실행 컨텍스트가 현재의 Lexical Scope를 기준으로 외부의 어떤 스코프에 접근 가능한지를 가리키는 참조이다. 각 실행 컨텍스트는 자신의 Outer Environment Reference(외부 환경 참조)를 가지고 있다. 여기서 우리는 Scope Chain의 개념을 알 수 있다. 찾고자하는 변수나 함수의 식별자를 현재 Lexical Environment부터 시작해서 상위 Lexical Environment로 거슬러 올라가며 탐색한다. 이때, Outer Environment Reference를 통해 외부 Lexical Environment에 접근할 수 있는 것이다.

만약 변수나 함수를 찾는 과정에서 현재 Lexical Environment와 그의 Outer Environment Reference에도 찾고자 하는 것이 없다면, Scope Chain을 계속 올라다가 전역 실행 컨텍스트까지 도달하게 된다. 더이상 상위 Lexical Environment가 없기 때문에 Outer Environment Referencenull이다.

function outerFunction() {
  var outerVariable = 'I am from outer!';
  
  function innerFunction() {
    var innerVariable = 'I am from inner!';
    console.log(outerVariable); // 외부 환경 참조를 통해 outerVariable에 접근
  }
  
  innerFunction();
}

outerFunction();

위 코드에서 innerFunctionouterFunction 내에서 정의되었으며, 이때 innerFunctionOuter Environment ReferenceouterFunction의 실행 컨텍스트를 참조한다. 따라서 innerFunction에서 outerVariable에 접근할 수 있게 된다.

Variable Environment

Variable Environment실행 컨텍스트 내에서 선언된 변수의 값들을 저장하는 환경이다. 주로 변수 선언과 초기화, 그리고 해당 변수에 대한 값들을 관리한다.

여기서 “그럼 환경 레코드(Environment Record)랑 무슨 차이지?” 라고 할 수 있는데 Variable Environment변수와 관련된 정보에 중점을 둔 환경을 나타내고, 환경 레코드라는 데이터 구조를 사용하여 변수와 식별자에 대한 정보를 저장하는 것이다.

기본적으로 Lexical Environment를 참조하고 있기 때문에 같은 환경이라고 할 수 있다. 따라서 Lexical Environment에서 설명한 모든 특성과 구성 요소가 동일하다. 다만 몇 가지 차이점이 존재한다.

  • Lexical Environment는 지속적으로 식별자를 업데이트해주지만 Variable Environment는 선언으로 만들어진 값을 유지한다.
  • ES6에서 Variable Environmentvar로 선언한 변수 정보를 저장하고, Lexical Environmentletconst로 선언한 변수와 함수 선언의 정보를 저장한다.

💡우선 이렇게 정의할 수 있는 이유를 살펴보기 전에 개념 하나만 알고 가자. 다음 변수 페이지에서 자세하게 다루겠지만 var로 선언한 변수는 함수 스코프를 가지고, ES6 이후 도입된 letconst로 선언한 변수는 블록 스코프를 가진다.

Variable Environment

주로 함수 스코프와 전역 스코프에서의 변수에 대한 정보를 저장하는 데 사용되는 환경 레코드의 집합을 나타낸다. 따라서 함수 스코프를 가지는 var로 선언한 변수를 저장한다.

Lexical Environment

letconst로 선언한 변수는 블록 내에서만 유효한 블록 스코프이다. Lexical Environment는 이러한 블록 스코프에서의 변수에 대한 정보를 포함한다. 따라서 letconst로 선언한 변수를 저장한다.

Private Environment

Private Environment클래스 내부에서 ClassElements에 의해 만들어진 Private Names의 정보를 저장하고 관리한다. 각 클래스는 자체의 Private Environment를 가지며, 이는 클래스의 Private Names에 대한 스코프와 접근을 제어한다. Private Environment는 클래스의 캡슐화와 정보 은폐를 지원하며, 클래스 내에서만 유효한 Private Names를 관리한다.

ClassElements에 의해 만들어진 Private Names는 클래스 내에서 private한 멤버를 나타내기 위해 사용되는 고유한 식별자이다.

const privateField = class {
  #value; // Private field using Private Name

  constructor(initialValue) {
    this.#value = initialValue;
  }

  getValue() {
    return this.#value;
  }
};

위 코드에서 #value는 Private Name을 통해 선언된 private 필드를 나타낸다. 클래스 외부에서는 #value에 직접 접근할 수 없으며, getValue 메서드를 통해서만 private 필드에 접근할 수 있다. (외부에서 Private Names에 직접 접근 불가능, 클래스의 인스턴스 메서드나 접근자를 통해서만 private 멤버에 접근 가능)

Private Environment Record

Private Environment RecordPrivate Environment의 일부로, 클래스 내에서 생성된 Private Names와 그에 대응하는 값들을 저장하는 레코드이다. 즉, Private EnvironmentPrivate Environment Record를 통해 Private Names에 접근하고 관리한다.

만약 현재 코드가 클래스 내에 위치하지 않은 경우, 즉, 가장 가까운 포함된 클래스가 없는 경우에는 Private Environment Record가 존재하지 않으며 null로 처리된다. 계속 나오는 개념이다. Private Names가 클래스 내부에서만 의미가 있고, 클래스 외부에서는 해당 Private Names에 대한 스코프 및 환경이 필요하지 않다는 얘기이다.

실행 컨텍스트 생성 과정

코드를 바탕으로 실행 컨텍스트 생성 과정을 설명해 보겠다.

const x = 1;

function calculateSum() {
  const y = 2;

  function sumNumbers() {
    const z = 3;
    console.log(x + y + z);
  }
  
  sumNumbers();
}

calculateSum(); // 6

전역 실행 컨텍스트 생성 단계

  1. 전역 실행 컨텍스트 객체가 생성된다.
  2. LexicalEnvironment 생성
    • Environment Record 생성: 전역 변수 x를 포함하는 환경 레코드가 생성된다.
    • Outer Environment Reference 설정: 전역 실행 컨텍스트의 외부 환경 참조는 null이다. (참고)
  3. VariableEnvironment 생성
    • LexicalEnvironment와 동일한 구조를 가진다.

전역 실행 컨텍스트 실행 단계

  1. Environment Record 초기화
    • 전역 변수 x가 메모리에 할당되고 1로 초기화된다.
    • 함수 calculateSum이 메모리에 할당되고 undefined로 초기화된다.
  2. this 바인딩 결정
    • 전역 컨텍스트에서 this는 전역 객체(window 또는 global 객체)를 가리킨다.
  3. 코드 실행
    • 전역 변수 x1이 할당된다.

calculateSum 실행 컨텍스트 생성 단계

  1. 실행 컨텍스트 객체 생성
    1. calculateSum 실행 컨텍스트 객체가 생성된다.
  2. LexicalEnvironment 생성
    • Environment Record 생성: 지역 변수 y를 포함하는 환경 레코드가 생성된다.
    • Outer Environment Reference 설정: calculateSum의 외부 환경 참조는 전역 실행 컨텍스트이다.
  3. VariableEnvironment 생성
    • LexicalEnvironment와 동일한 구조를 가진다.

calculateSum 실행 컨텍스트 실행 단계

  1. Environment Record 초기화
    • 지역 변수 y가 메모리에 할당되고 2로 초기화된다.
    • 함수 sumNumbers가 메모리에 할당되고 undefined로 초기화된다.
  2. this 바인딩 결정
    • calculateSum 내부에서 this 실행 시점에 결정된다.
  3. 코드 실행
    • y2가 할당된다.
    • sumNumbers가 호출된다.

sumNumbers 실행 컨텍스트 생성 단계

  1. 실행 컨텍스트 객체 생성
    • sumNumbers 실행 컨텍스트 객체가 생성된다.
  2. LexicalEnvironment 생성
    • Environment Record 생성: 지역 변수 z를 포함하는 환경 레코드가 생성된다.
    • Outer Environment Reference 설정: sumNumbers의 외부 환경 참조는 calculateSum 실행 컨텍스트이다.
  3. VariableEnvironment 생성
    • LexicalEnvironment와 동일한 구조를 가진다.

sumNumbers 실행 컨텍스트 실행 단계

  1. Environment Record 초기화
    • 지역 변수 z가 메모리에 할당되고 3으로 초기화된다.
  2. this 바인딩 결정
    • sumNumbers 내부에서 this 실행 시점에 결정된다.
  3. 코드 실행
    • z3이 할당된다.
    • console.log(z + y + z); 실행 시 1 + 2 + 3 = 6이 출력된다.

정리

  1. 실행 컨텍스트는 JavaScript 코드가 실행되는 환경이고, 코드 타입(전역 코드, 함수 코드, eval 함수 코드)에 따라 각각 다른 실행 컨텍스트를 생성한다. 생성된 실행 컨텍스트는 콜 스택에 쌓인다.
  2. 코드의 실행 순서는 콜 스택으로 관리하고, 식별자와 스코프는 실행 컨텍스트의 Lexical Environment으로 관리한다.
  3. Lexical Environment는 실행 컨텍스트 내에서 변수와 함수가 어디에 선언되었는지에 따라 동적으로 변경된다. 즉, 스코프를 표현하며 Lexical Environment가 어디인지에 따라서 결정된다.
  4. Environment Record는 변수와 함수의 선언에 대한 정보를 저장하고 전역 환경 레코드, 객체 환경 레코드, 선언적 환경 레코드, 그리고 함수 환경 레코드 등이 있다.
  5. 호이스팅 개념 등장. 실행 컨텍스트 관점에서 환경 레코드를 통해 이미 식별자들의 정보를 파악하고 있기 때문에 var로 선언한 변수, 함수 선언문 등이 있어도 먼저 호출할 수 있다.
  6. 다만, 선언과 초기화가 분리되어 진행되는 let, const로 선언한 변수는 값이 초기화되지 않아 ReferenceError를 발생하는 것이지 호이스팅은 일어난 것이다. ReferenceError가 발생하는 이유는이때 변수는 TDZ 상태에 있기 때문이다.
👈🏻 호이스팅
  👉🏻 변수