🤔 Recap
이전 글에서는 JavaScript 실행 컨텍스트의 개념과 구성요소, Creation Phase와 Execution Phase에 대해 알아보았습니다.
다시 한번 정리하자면,
실행 컨텍스트는
- JavaScript 코드가 Isolate 하게 실행되는 환경을 가리키는 Realm
let,const,class,함수표현식을 위한 식별자 바인딩을 저장하는 Lexical Environmentvar,함수선언문을 위한 식별자 바인딩을 저장하는 Variable Environment
으로 구성되어 있고,
- 스크립트가 실행되거나,
- 함수가 호출되거나,
- 모듈이 로드될때,
eval()함수가 호출될 때
실행컨텍스트가 생성 (Creation Phase) 되며, Lexical Environment와 Variable Environment가 생성되고,
식별자를 스캔하여 메모리 공간을 예약합니다. (이때, Hoisting 이 발생합니다)
🔎 예제로 알아보는 실행컨텍스트의 동작원리
이번 글에서는 간단한 예제코드를 바탕으로 실행 컨텍스트가 어떻게 생성되고, 식별자를 찾는 과정, 클로저와 호이스팅이 어떻게 동작하는지 단계별로 시각화해보겠습니다.
var message = "안녕하세요! ";
const firstName = "대건";
let lastName = "김";
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}
function greet(baseGreetMessage) {
const person = new Person(firstName, lastName);
function createGreetingMessage() {
return `${baseGreetMessage} ${person.getFullName()}`;
}
return createGreetingMessage;
}
const sayHello = greet(message);
console.log(sayHello()); // "안녕하세요! 대건 김"1. Script Loaded
가장 먼저 스크립트가 로드되면, Realm 이 생성됩니다.
내장객체 (Intrinsics) 와 전역객체 (Global Object) 가 생성되고,
Global Environment Record 에는 [[Declarative Record]] [[Object Record]] [[Global This]] [[Outer Env Reference]] 슬롯이 생성됩니다.
현재 스크립트가 최초로 로드되었으므로,
Realm 의 [[Outer Env Reference]] 슬롯은 null 을 참조합니다.
(즉, 외부 Environment Record 가 없으므로)
2. Global Execution Context - Creation Phase
Realm 이 생성되면, Global Execution Context 가 생성(Creation Phase)됩니다.
Global Execution Context 의 Lexical Environment 는 Realm 의 Global Environment Record 의 [[Declarative Record]] 슬롯을 참조합니다.
Variable Environment 는 Realm 의 Global Environment Record 의 [[Object Record]] 슬롯을 참조합니다.
Creation Phase 에서는 식별자들을 스캔하여 메모리 공간을 예약합니다.
message:var키워드로 선언되었으므로,Variable Environment Record에 등록되고undefined로 초기화됩니다.firstName:const키워드로 선언되었으므로,Lexical Environment Record에 등록되고Temporal Dead Zone(TDZ)상태가 됩니다.
(이때 변수에 접근시 Reference Error 발생)lastName:let키워드로 선언되었으므로,Lexical Environment Record에 등록되고Temporal Dead Zone(TDZ)상태가 됩니다.
(이때 변수에 접근시 Reference Error 발생)Person:class키워드로 선언되었으므로,Lexical Environment Record에 등록되고Temporal Dead Zone(TDZ)상태가 됩니다.
(이때 변수에 접근시 Reference Error 발생)greet:function declaration으로 선언되었으므로,Variable Environment Record에 등록되고 ‼️함수객체가 Heap 영역에 생성된 뒤 곧바로 바인딩‼️ 됩니다.sayHello:const키워드로 선언되었으므로,Lexical Environment Record에 등록되고Temporal Dead Zone(TDZ)상태가 됩니다.
(이때 변수에 접근시 Reference Error 발생)
3. Global Execution Context - Execution Phase
Creation Phase 가 끝나면, 실제 코드가 실행되는 Execution Phase 가 실행됩니다
생성된 Global Execution Context 는 콜스택에 쌓이고, 실행됩니다.
message: "안녕하세요! " 문자열이 Heap 영역에 생성되고,message변수는 해당 값을 가리킵니다.firstName: "대건" 문자열이 Heap 영역에 생성되고,firstName변수는 해당 값을 가리킵니다.lastName: "김" 문자열이 Heap 영역에 생성되고,lastName변수는 해당 값을 가리킵니다.Person: Person 클래스의 생성자 함수와getFullName메서드가 Heap 영역에 생성되고,Person의 프로토타입 체인에 연결됩니다.greet:greet함수는 Creation Phase 에서 이미 Heap 영역에 생성되었으므로 건너뜁니다sayHello:greet함수가 호출되기 전까지는Temporal Dead Zone(TDZ)상태입니다.
여기서, sayHello 초기화를 위해 greet 함수가 호출되면, 새로운 Function Execution Context 가 생성됩니다.
Function Execution Context 또한 Creation Phase 와 Execution Phase 를 거칩니다.
4. greet Function Execution Context - Creation Phase
greet 함수가 호출되기위해 greet Function Execution Context 의 Creation Phase 가 시작됩니다.
baseGreetMessage:[[Parameter]](또는[[Arguments]]) 슬롯으로부터baseGreetMessage에 대한 식별자 바인딩이Lexical Environment Record에 생성됩니다. (단, 함수의 파라미터는 Creation Phase 에서 즉시 인수값으로 초기화됩니다)person:const키워드로 선언되었으므로,Lexical Environment Record에 등록되고Temporal Dead Zone(TDZ)상태가 됩니다. (이때 변수에 접근시 Reference Error 발생)createGreetingMessage: 함수 선언문이므로, greet Function Execution Context 의Variable Environment에 등록되고,createGreetingMessage함수 객체가 Heap 영역에 생성됩니다.
이때,createGreetingMessage함수의[[Scope]](또는 ES6[[Environment]]) 슬롯은greet함수의 Lexical Environment 를 참조합니다. (‼️ 클로저 생성 ‼️)[[Outer Env Reference]]:greet함수의Lexical Environment의[[Outer Env Reference]]는 상위 스코프인Global Execution Context의Lexical Environment를 참조합니다.
5. greet Function Execution Context - Execution Phase
greet 함수의 Execution Context Creation Phase 가 끝났으므로, 콜스택에 쌓이고, Execution Phase 가 시작됩니다.
person:new Person(firstName, lastName)를 통해Person클래스의 인스턴스가 생성되고,person변수는 해당 인스턴스를 가리킵니다.‼️ 스코프 체이닝 : 식별자를 찾는 과정 ‼️
1️⃣firstName과lastName식별자는 현재greet함수의Lexical Environment에 존재하지 않으므로,
2️⃣greet함수의Lexical Environment의[[Outer Env Reference]]슬롯을 통해 상위 Lexical Environment 인
Global Execution Context의Lexical Environment로 올라갑니다.
3️⃣Global Execution Context의Lexical Environment에서firstName과lastName을 찾을 수 있으므로,
firstName과lastName은 각각 "대건" 과 "김" 으로 초기화됩니다.
return createGreetingMessage: Heap 영역에 생성된createGreetingMessage함수 객체를 반환합니다.
6. Global Execution Context - Execution Phase (계속)
greet 함수의 Execution Phase 가 끝나고, sayHello 변수에 createGreetingMessage 함수 객체가 바인딩됩니다. console.log(sayHello()) 를 통해 sayHello 함수가 호출되면, 새로운 Function Execution Context 가 생성됩니다.
7. createGreetingMessage Function Execution Context - Creation Phase
createGreetingMessage 함수가 호출되기위해 createGreetingMessage Function Execution Context 의 Creation Phase 가 시작됩니다.
createGreetingMessage 함수의 Lexical Environment 는 greet 함수의 Lexical Environment 를 참조합니다. (‼️ 4-3 에서 생성된 Closure)
이를 통해 baseGreetMessage 와 person 식별자에 접근할 수 있습니다.
8. createGreetingMessage Function Execution Context - Execution Phase
createGreetingMessage 함수의 Execution Context Creation Phase 가 끝났으므로, 콜스택에 쌓이고, Execution Phase 가 시작됩니다.
baseGreetMessage: (‼️ 4-3 에서 생성된 Closure 를 통해)greet함수의Lexical Environment에 접근하여,baseGreetMessage는 "안녕하세요! " 가 됩니다person: (‼️ 4-3 에서 생성된 Closure 를 통해)greet함수의Lexical Environment에 접근하여,person은Person클래스의 인스턴스를 가리킵니다.return \`${baseGreetMessage} ${person.getFullName()}\:getFullName메서드를 호출하여,person인스턴스의firstName과lastName을 가져와서,
${baseGreetMessage} ${person.getFullName()}를 반환합니다.
console.log(sayHello()) 에서 createGreetingMessage 함수가 반환한 값은 "안녕하세요! 대건 김" 이 됩니다.
9. Global Execution Context - Execution Phase (계속)
console:console식별자를 찾기위해 Global Execution Context 의Lexical Environment를 참조하고, 이는 Global Object 를 참조합니다.log:console식별자에 바인딩된 Global Object 의log메서드를 호출합니다.sayHello():createGreetingMessage함수가 반환한 값인 "안녕하세요! 대건 김" 을 인자로 전달합니다.console.log(sayHello()): "안녕하세요! 대건 김" 이 콘솔에 출력됩니다.
📝 마무리
다시한번 정리하자면,
실행 컨텍스트는 JavaScript 의 독립된 실행환경인 Realm
let,const,class식별자 바인딩이 저장되는 Lexical Environment
var,함수 선언문에 대한 식별자 바인딩이 저장되는 Variable Environment
로 구성됩니다실행컨텍스트는 스크립트 로드, 함수 실행시 식별자를 등록하고 메로리를 예약하는 Creation Phase와
실제 코드가 실행되는 Execution Phase 를 거친다 이때, Creation Phase 에서 ‼️호이스팅‼️ 이 발생한다Environment Record 는 상위 실행 컨텍스트의 Environment Record 를 가리키는 [[Outer Environment Reference]] 가 존재한다
Execution Phase 에서 현재 실행컨텍스트의 Environment Record 에서 식별자를 찾고,
존재하지 않는 경우 [[Outer Environment Reference]] 를 통해 상위 실행컨텍스트에서 식별자를 찾는다 (‼️스코프 체인‼️)함수 내부에 다른 함수가 정의된 경우, 내부 함수의 Environment Record 는
상위 함수 실행 컨텍스트의 Environment Record 를 캡쳐하고, [[Outer Environment Reference]] 로 참조한다 이를 통해, 내부 함수는 상위함수의 Environment Record 에 접근 가능하다 (‼️클로저‼️)