실행 컨텍스트란(Execution Context)
JavaScript 엔진은 코드를 해석하고 실행하는 복잡한 특성을 관리하기 위해 실행 컨텍스트를 사용한다. 코드를 실행하기 위해서는 변수, 함수, 스코프, 식별자, 코드 실행 순서 등의 정보를 알아야 하며 관리도 필요하다. 실행 컨텍스트는 이러한 것들을 관리한다. 즉, JavaScript 코드가 실행되는 환경이다. 또한, 코드를 평가하고 실행에 필요한 정보들을 모아놓은 객체이다.
호이스팅에 대해 학습하는 동안 “실행 컨텍스트” 라는 용어가 여러 번 등장했다. 잠깐 들여다봐도 쉬운 개념이 아니었다. 실행 컨텍스트를 바르게 이해하면 코드의 실행 흐름, 호이스팅이 발생하는 이유, 식별자와 식별자 바인딩을 관리하는 방식 등 JavaScript의 핵심 원리와 여러 가지 특징들을 파악하는 데 매 우 도움이 된다.
실행 컨텍스트 종류
JavaScript 엔진은 실행 가능한 코드를 읽기 시작하면 그 소스 코드를 평가한다. JavaScript에서 실행 가능한 코드란 뭘까?
- 전역 코드
- 함수 코드
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 엔진은 어떻게 동작하는가?
💡 JavaScript 엔진은 소스 코드를 평가 및 실행(Runtime) 과정으로 나눠서 처리하여 코드를 해석하고 실행한다. 이어서 학습할 내용들을 위해 미리 이해하고 있자.
평가 과정
JavaScript 엔진이 실행 컨텍스트를 생성하고, 변수와 함수의 선언문만 먼저 확인해서 식별자를 키로 실행 컨텍스트가 관리하는 *렉시컬 환경의 *환경 레코드에 등록한다. 코드의 실행 순서는 실행 컨텍스트 스택을 통해 관리한다.
다시 말해 코드 실행 전에 코드를 먼저 평가하는 것인데, 실행 컨텍스트 스택을 통해 코드를 읽고 컴파일 과정을 거치면서 변수, 함수 등의 선언문을 파악하되 코드를 실행하지는 않는다.
실행 과정
평가 과정이 끝나면 선언문을 제외한 소스 코드가 순차적으로 실행된다. 이를 런타임(Runtime)이라고 한다. 소스 코드 실행에 필요한 변수나 함수의 참조는 실행 컨텍스트가 관리하는 환경 레코드에서 검색하여 사용한다.
이렇게 생성된 실행 컨텍스트들은 실행 컨텍스트 스택에 쌓인다. 과정을 알아보자.
실행 컨텍스트 스택
콜 스택(Call stack)
이라 불리는 자료구조에 생성된 실행 컨텍스트가 쌓인다. 스택 자료구조는 LIFO(Last In, First Out)을 따르고 있어 코드 실행 순서를 보장한다. 콜 스택 내부에 쌓인 실행 컨텍스트의 정보를 통해 JavaScript 엔진은 어떤 함수가 실행 중인지, 호출되었는지 등을 추적하며 코드의 환경과 순서를 보장한다.
콜 스택 동작
JavaScript 엔진이 처음 코드를 실행하면 전역 실행 컨텍스트
를 생성하고 이를 콜 스택
에 추가(push)한다. 그 후 JavaScript 엔진이 함수를 호출할 때마다 그 함수를 위한 함수 실행 컨텍스트
를 생성하고 이를 콜 스택
에 추가한다. JavaScript 엔진은 콜 스택
의 최상단에 위치한 함수를 실행하며 함수가 종료되면 콜 스택
에서 제거(pop)하고 다음 최상단에 위치한 함수로 이동한다.
코드와 그림을 통해 실행 컨텍스트가 스택에 쌓이는 과정을 알아보자.
Step | Description | |
0 | 코드 실행 전 | 콜 스택은 전부 비워진 상태로 시작한다. |
1 | 전역 코드 평가 및 실행 | - 전역 코드를 평가해서 전역 실행 컨텍스트 를 생성하고 콜 스택에 추가한다.
- 전역 코드에는 변수 x 와 함수 foo 가 있고, 이를 전역 실행 컨텍스트 에 등록한다.
- 평가가 끝나면 전역 코드를 실행하고 전역 변수 x 에 1을 할당, 전역 함수인 foo 를 호출한다. |
2 | foo 함수 코드 평가 및 실행 | - foo 함수가 호출되면 foo 함수 실행 컨텍스트 를 생성하고 콜 스택에 추가한다. 이때 전역 코드의 실행은 잠시 중단되고, 코드 제어권이 foo 함수로 넘어간다.
- 지역 변수 y 와 지역 함수 bar 가 foo 함수 실행 컨텍스트 에 등록된다.
- foo 함수의 평가가 끝났으면 foo 함수를 실행해서 bar 함수 실행 코드를 만나게 된다. |
3 | bar 함수 코드 평가 및 실행 | - bar 함수가 호출되면 bar 함수 실행 컨텍스트 를 생성하고 콜 스택에 추가한다. 이때 foo 함수 실행은 잠시 중단되고, 제어권이 bar 함수 내부로 넘어간다.
- bar 함수 내부의 코드를 평가하고 지역 변수 z 가 bar 함수 실행 컨텍스트 에 등록된다.
- bar 함수를 실행한다. 변수 z 에 3이 할당되고 console.log 메서드를 호출한 이후 bar 함수는 종료된다. |
4 | foo 함수 코드로 복귀 | - bar 함수가 종료된 후 콜 스택에서 bar 함수 실행 컨텍스트 를 제거한다.
- 코드의 제어권은 다시 foo 함수로 넘어간다.
- 그리고 foo 함수 또한 더 이상 실행할 코드가 없으므로 종료된다. |
5 | 전역 코드로 복귀 | - foo 함수가 종료된 후 콜 스택에서 foo 함수 실행 컨텍스트 를 제거한다.
- 코드의 제어권은 전역 코드로 넘어간다.
- 전역 코드에서도 더 이상 실행할 코드가 없으므로 종료된다. |
6 | 코드 실행 완료 | - 전역 코드가 종료된 후 콜 스택에서 전역 실행 컨텍스트 를 제거한다. 콜 스택은 모두 비워졌다. |
JavaScript 엔진이 실행 컨텍스트 스택을 통해서 어떻게 실행 컨텍스트를 관리하는지 살펴보았다. 그렇다면 실행 컨텍스트 내부에는 어떤 정보가 담길까? 어떤 구조로 되어 있을까? 이제 실행 컨텍스트 구조에 대해 알아볼 것이다. 또한 코드를 평가하는 과정과 코드 실행에 따른 실행 컨텍스트의 변화에 대해서도 알아보자.
실행 컨텍스트 구조
사실 학습하는 과정에서 혼돈의 카오스였다. 실행 컨텍스트 컴포넌트 구조는 ECMAScript 버전마다 바뀌었는데 최신 블로그들을 보면 여러 버전의 ES가 섞여 있는 글들이 많았다. 나는 ES2022(ECMAScript 2022) 버전 기준으로 설명하려고 한다. (참고: https://velog.io/@kdeun1/ECMAScript-버전별-Execution-Context-정리)
ES5 이전 실행 컨텍스트 구조
ExecutionContext: {
변수 객체,
스코프 체인,
this 바인딩
}
- 변수 객체(Variable Object)
- 스코프 체인(Scope Chain)
this
바인딩
변수, 매개변수(parameters)와 인수(arguments) 정보, 함수 선언의 정보들을 가진 객체이다. 즉, 코드가 실행될 때 필요한 각종 정보들을 담아두는 곳이다.
스코프는 현재 실행 중인 컨텍스트의 유효한 범위를 말한다. 실행 컨텍스트에서 현재 스코프의 변수 객체뿐만 아니라 외부 스코프의 변수 객체에도 접근(검색)할 수 있도록 관계를 연결한 것이 스코프 체인이다.
즉, 스코프 체인은 변수 및 함수 식별자를 찾을 때 현재 스코프부터 시작해 외부 스코프로 계속 거슬러 올라가며 검색하는 매커니즘이다. 이를 통해 함수 내부에서 외부 변수에 접근이 가능하다.
this
가 어떤 객체를 참조하고 있는지에 대한 정보를 관리한다.
ES5 이후 실행 컨텍스트 구조
ES5 이후 let
과 const
같은 블록 스코프 개념이 추가되면서 큼지막한 구조는 아래와 비슷하나 내부 컴포넌트가 조금씩 바뀌어 왔고, 현재 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
의 프로퍼티가 된다.BindingObject
프로퍼티는 전역 객체(window
또는global
)를 참조하기 때문에var
로 선언한 변수나 전역 함수 등은BindingObject
를 통해서 전역 객체의 프로퍼티로 등록된다.- 따라서,
var
로 선언된 전역 변수와 함수 선언문으로 정의된 전역 함수, 객체 등은window
의 프로퍼티로 접근할 수 있는 것이다.
GlobalExecutionContext: {
GlobalLexicalEnvironment: {
GlobalEnvironmentRecord: {
ObjectEnvironmentRecord: {
BindingObject
}
}
}
}
// 전역 변수 선언
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
로 선언된 전역 변수를 등록하고 관리한다.
let
과 const
로 선언된 변수는 선언과 초기화가 분리되어 진행된다. 평가 단계에서는 선언만이 이루어지며, 이때 변수는 식별자로 등록되어 *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 Reference
는 null
이다.
function outerFunction() {
var outerVariable = 'I am from outer!';
function innerFunction() {
var innerVariable = 'I am from inner!';
console.log(outerVariable); // 외부 환경 참조를 통해 outerVariable에 접근
}
innerFunction();
}
outerFunction();
위 코드에서 innerFunction
은 outerFunction
내에서 정의되었으며, 이때 innerFunction
의 Outer Environment Reference
는 outerFunction
의 실행 컨텍스트를 참조한다. 따라서 innerFunction
에서 outerVariable
에 접근할 수 있게 된다.
Variable Environment
Variable Environment
는 실행 컨텍스트 내에서 선언된 변수의 값들을 저장하는 환경이다. 주로 변수 선언과 초기화, 그리고 해당 변수에 대한 값들을 관리한다.
여기서 “그럼 환경 레코드(Environment Record)
랑 무슨 차이지?” 라고 할 수 있는데 Variable Environment
는 변수와 관련된 정보에 중점을 둔 환경을 나타내고, 환경 레코드
라는 데이터 구조를 사용하여 변수와 식별자에 대한 정보를 저장하는 것이다.
기본적으로 Lexical Environment
를 참조하고 있기 때문에 같은 환경이라고 할 수 있다. 따라서 Lexical Environment
에서 설명한 모든 특성과 구성 요소가 동일하다. 다만 몇 가지 차이점이 존재한다.
Lexical Environment
는 지속적으로 식별자를 업데이트해주지만Variable Environment
는 선언으로 만들어진 값을 유지한다.- ES6에서
Variable Environment
는var
로 선언한 변수 정보를 저장하고,Lexical Environment
는let
과const
로 선언한 변수와 함수 선언의 정보를 저장한다.
💡우선 이렇게 정의할 수 있는 이유를 살펴보기 전에 개념 하나만 알고 가자. 다음 변수 페이지에서 자세하게 다루겠지만 var
로 선언한 변수는 함수 스코프를 가지고, ES6 이후 도입된 let
과 const
로 선언한 변수는 블록 스코프를 가진다.
Variable Environment
주로 함수 스코프와 전역 스코프에서의 변수에 대한 정보를 저장하는 데 사용되는 환경 레코드의 집합을 나타낸다. 따라서 함수 스코프를 가지는 var
로 선언한 변수를 저장한다.
Lexical Environment
let
과 const
로 선언한 변수는 블록 내에서만 유효한 블록 스코프이다. Lexical Environment
는 이러한 블록 스코프에서의 변수에 대한 정보를 포함한다. 따라서 let
과 const
로 선언한 변수를 저장한다.
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 Record
는 Private Environment
의 일부로, 클래스 내에서 생성된 Private Names와 그에 대응하는 값들을 저장하는 레코드이다. 즉, Private Environment
는 Private 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
전역 실행 컨텍스트 생성 단계
- 전역 실행 컨텍스트 객체가 생성된다.
- LexicalEnvironment 생성
- Environment Record 생성: 전역 변수
x
를 포함하는 환경 레코드가 생성된다. - Outer Environment Reference 설정: 전역 실행 컨텍스트의 외부 환경 참조는
null
이다. (참고) - VariableEnvironment 생성
- LexicalEnvironment와 동일한 구조를 가진다.
전역 실행 컨텍스트 실행 단계
- Environment Record 초기화
- 전역 변수
x
가 메모리에 할당되고1
로 초기화된다. - 함수
calculateSum
이 메모리에 할당되고undefined
로 초기화된다. - this 바인딩 결정
- 전역 컨텍스트에서
this
는 전역 객체(window
또는global
객체)를 가리킨다. - 코드 실행
- 전역 변수
x
에1
이 할당된다.
calculateSum 실행 컨텍스트 생성 단계
- 실행 컨텍스트 객체 생성
calculateSum 실행 컨텍스트
객체가 생성된다.- LexicalEnvironment 생성
- Environment Record 생성: 지역 변수
y
를 포함하는 환경 레코드가 생성된다. - Outer Environment Reference 설정:
calculateSum
의 외부 환경 참조는전역 실행 컨텍스트
이다. - VariableEnvironment 생성
- LexicalEnvironment와 동일한 구조를 가진다.
calculateSum 실행 컨텍스트 실행 단계
- Environment Record 초기화
- 지역 변수
y
가 메모리에 할당되고2
로 초기화된다. - 함수
sumNumbers
가 메모리에 할당되고undefined
로 초기화된다. - this 바인딩 결정
calculateSum
내부에서this
실행 시점에 결정된다.- 코드 실행
y
에2
가 할당된다.sumNumbers
가 호출된다.
sumNumbers 실행 컨텍스트 생성 단계
- 실행 컨텍스트 객체 생성
sumNumbers 실행 컨텍스트
객체가 생성된다.- LexicalEnvironment 생성
- Environment Record 생성: 지역 변수
z
를 포함하는 환경 레코드가 생성된다. - Outer Environment Reference 설정:
sumNumbers
의 외부 환경 참조는calculateSum 실행 컨텍스트
이다. - VariableEnvironment 생성
- LexicalEnvironment와 동일한 구조를 가진다.
sumNumbers 실행 컨텍스트 실행 단계
- Environment Record 초기화
- 지역 변수
z
가 메모리에 할당되고3
으로 초기화된다. - this 바인딩 결정
sumNumbers
내부에서 this 실행 시점에 결정된다.- 코드 실행
z
에3
이 할당된다.console.log(z + y + z);
실행 시1 + 2 + 3 = 6
이 출력된다.
정리
- 실행 컨텍스트는 JavaScript 코드가 실행되는 환경이고, 코드 타입(전역 코드, 함수 코드,
eval
함수 코드)에 따라 각각 다른 실행 컨텍스트를 생성한다. 생성된 실행 컨텍스트는 콜 스택에 쌓인다. - 코드의 실행 순서는 콜 스택으로 관리하고, 식별자와 스코프는 실행 컨텍스트의 Lexical Environment으로 관리한다.
- Lexical Environment는 실행 컨텍스트 내에서 변수와 함수가 어디에 선언되었는지에 따라 동적으로 변경된다. 즉, 스코프를 표현하며 Lexical Environment가 어디인지에 따라서 결정된다.
- Environment Record는 변수와 함수의 선언에 대한 정보를 저장하고 전역 환경 레코드, 객체 환경 레코드, 선언적 환경 레코드, 그리고 함수 환경 레코드 등이 있다.
- 호이스팅 개념 등장. 실행 컨텍스트 관점에서 환경 레코드를 통해 이미 식별자들의 정보를 파악하고 있기 때문에
var
로 선언한 변수, 함수 선언문 등이 있어도 먼저 호출할 수 있다. - 다만, 선언과 초기화가 분리되어 진행되는
let
,const
로 선언한 변수는 값이 초기화되지 않아ReferenceError
를 발생하는 것이지 호이스팅은 일어난 것이다.ReferenceError
가 발생하는 이유는이때 변수는 TDZ 상태에 있기 때문이다.