Skip to content

JavaScript Execution Context 실행 컨텍스트 - 예제편 (feat. 호이스팅, 스코프 체인, 클로저)

🤔 Recap

이전 글에서는 JavaScript 실행 컨텍스트의 개념과 구성요소, Creation Phase와 Execution Phase에 대해 알아보았습니다.

다시 한번 정리하자면,

실행 컨텍스트는

  1. JavaScript 코드가 Isolate 하게 실행되는 환경을 가리키는 Realm
  2. let, const, class, 함수표현식 을 위한 식별자 바인딩을 저장하는 Lexical Environment
  3. var, 함수선언문 을 위한 식별자 바인딩을 저장하는 Variable Environment

으로 구성되어 있고,

  1. 스크립트가 실행되거나,
  2. 함수가 호출되거나,
  3. 모듈이 로드될때,
  4. eval() 함수가 호출될 때

실행컨텍스트가 생성 (Creation Phase) 되며, Lexical Environment와 Variable Environment가 생성되고,
식별자를 스캔하여 메모리 공간을 예약합니다. (이때, Hoisting 이 발생합니다)

🔎 예제로 알아보는 실행컨텍스트의 동작원리

이번 글에서는 간단한 예제코드를 바탕으로 실행 컨텍스트가 어떻게 생성되고, 식별자를 찾는 과정, 클로저와 호이스팅이 어떻게 동작하는지 단계별로 시각화해보겠습니다.

javascript
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 에서는 식별자들을 스캔하여 메모리 공간을 예약합니다.

  1. message : var 키워드로 선언되었으므로, Variable Environment Record 에 등록되고 undefined 로 초기화됩니다.
  2. firstName : const 키워드로 선언되었으므로, Lexical Environment Record 에 등록되고 Temporal Dead Zone(TDZ) 상태가 됩니다.
    (이때 변수에 접근시 Reference Error 발생)
  3. lastName : let 키워드로 선언되었으므로, Lexical Environment Record 에 등록되고 Temporal Dead Zone(TDZ) 상태가 됩니다.
    (이때 변수에 접근시 Reference Error 발생)
  4. Person : class 키워드로 선언되었으므로, Lexical Environment Record 에 등록되고 Temporal Dead Zone(TDZ) 상태가 됩니다.
    (이때 변수에 접근시 Reference Error 발생)
  5. greet : function declaration 으로 선언되었으므로, Variable Environment Record 에 등록되고 ‼️함수객체가 Heap 영역에 생성된 뒤 곧바로 바인딩‼️ 됩니다.
  6. sayHello : const 키워드로 선언되었으므로, Lexical Environment Record 에 등록되고 Temporal Dead Zone(TDZ) 상태가 됩니다.
    (이때 변수에 접근시 Reference Error 발생)

3. Global Execution Context - Execution Phase

Creation Phase 가 끝나면, 실제 코드가 실행되는 Execution Phase 가 실행됩니다
생성된 Global Execution Context 는 콜스택에 쌓이고, 실행됩니다.

  1. message : "안녕하세요! " 문자열이 Heap 영역에 생성되고, message 변수는 해당 값을 가리킵니다.
  2. firstName : "대건" 문자열이 Heap 영역에 생성되고, firstName 변수는 해당 값을 가리킵니다.
  3. lastName : "김" 문자열이 Heap 영역에 생성되고, lastName 변수는 해당 값을 가리킵니다.
  4. Person : Person 클래스의 생성자 함수와 getFullName 메서드가 Heap 영역에 생성되고, Person 의 프로토타입 체인에 연결됩니다.
  5. greet : greet 함수는 Creation Phase 에서 이미 Heap 영역에 생성되었으므로 건너뜁니다
  6. 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 가 시작됩니다.

  1. baseGreetMessage : [[Parameter]] (또는 [[Arguments]]) 슬롯으로부터 baseGreetMessage 에 대한 식별자 바인딩이 Lexical Environment Record에 생성됩니다. (단, 함수의 파라미터는 Creation Phase 에서 즉시 인수값으로 초기화됩니다)
  2. person : const 키워드로 선언되었으므로, Lexical Environment Record 에 등록되고 Temporal Dead Zone(TDZ) 상태가 됩니다. (이때 변수에 접근시 Reference Error 발생)
  3. createGreetingMessage : 함수 선언문이므로, greet Function Execution Context 의 Variable Environment 에 등록되고, createGreetingMessage 함수 객체가 Heap 영역에 생성됩니다.
    이때, createGreetingMessage 함수의 [[Scope]](또는 ES6 [[Environment]]) 슬롯은 greet 함수의 Lexical Environment 를 참조합니다. (‼️ 클로저 생성 ‼️)
  4. [[Outer Env Reference]] : greet 함수의 Lexical Environment[[Outer Env Reference]] 는 상위 스코프인 Global Execution ContextLexical Environment 를 참조합니다.

5. greet Function Execution Context - Execution Phase

greet 함수의 Execution Context Creation Phase 가 끝났으므로, 콜스택에 쌓이고, Execution Phase 가 시작됩니다.

  1. person : new Person(firstName, lastName) 를 통해 Person 클래스의 인스턴스가 생성되고, person 변수는 해당 인스턴스를 가리킵니다.

   ‼️ 스코프 체이닝 : 식별자를 찾는 과정 ‼️
   1️⃣firstNamelastName 식별자는 현재 greet 함수의 Lexical Environment 에 존재하지 않으므로,
   2️⃣greet 함수의 Lexical Environment[[Outer Env Reference]] 슬롯을 통해 상위 Lexical Environment
   Global Execution ContextLexical Environment 로 올라갑니다.
   3️⃣Global Execution ContextLexical Environment 에서 firstNamelastName 을 찾을 수 있으므로,
   firstNamelastName 은 각각 "대건" 과 "김" 으로 초기화됩니다.

  1. 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 Environmentgreet 함수의 Lexical Environment 를 참조합니다. (‼️ 4-3 에서 생성된 Closure)
이를 통해 baseGreetMessageperson 식별자에 접근할 수 있습니다.

8. createGreetingMessage Function Execution Context - Execution Phase

createGreetingMessage 함수의 Execution Context Creation Phase 가 끝났으므로, 콜스택에 쌓이고, Execution Phase 가 시작됩니다.

  1. baseGreetMessage : (‼️ 4-3 에서 생성된 Closure 를 통해) greet 함수의 Lexical Environment 에 접근하여, baseGreetMessage 는 "안녕하세요! " 가 됩니다
  2. person : (‼️ 4-3 에서 생성된 Closure 를 통해) greet 함수의 Lexical Environment 에 접근하여, personPerson 클래스의 인스턴스를 가리킵니다.
  3. return \`${baseGreetMessage} ${person.getFullName()}\ : getFullName 메서드를 호출하여, person 인스턴스의 firstNamelastName 을 가져와서,
    ${baseGreetMessage} ${person.getFullName()} 를 반환합니다.

console.log(sayHello()) 에서 createGreetingMessage 함수가 반환한 값은 "안녕하세요! 대건 김" 이 됩니다.

9. Global Execution Context - Execution Phase (계속)

  1. console : console 식별자를 찾기위해 Global Execution Context 의 Lexical Environment 를 참조하고, 이는 Global Object 를 참조합니다.
  2. log : console 식별자에 바인딩된 Global Object 의 log 메서드를 호출합니다.
  3. sayHello() : createGreetingMessage 함수가 반환한 값인 "안녕하세요! 대건 김" 을 인자로 전달합니다.
  4. console.log(sayHello()) : "안녕하세요! 대건 김" 이 콘솔에 출력됩니다.

📝 마무리

다시한번 정리하자면,

  1. 실행 컨텍스트는 JavaScript 의 독립된 실행환경인 Realm
       let, const, class 식별자 바인딩이 저장되는 Lexical Environment
       var, 함수 선언문에 대한 식별자 바인딩이 저장되는 Variable Environment
       로 구성됩니다

  2. 실행컨텍스트는 스크립트 로드, 함수 실행시 식별자를 등록하고 메로리를 예약하는 Creation Phase
       실제 코드가 실행되는 Execution Phase 를 거친다    이때, Creation Phase 에서 ‼️호이스팅‼️ 이 발생한다

  3. Environment Record 는 상위 실행 컨텍스트의 Environment Record 를 가리키는 [[Outer Environment Reference]] 가 존재한다

  4. Execution Phase 에서 현재 실행컨텍스트의 Environment Record 에서 식별자를 찾고,
       존재하지 않는 경우 [[Outer Environment Reference]] 를 통해 상위 실행컨텍스트에서 식별자를 찾는다 (‼️스코프 체인‼️)

  5. 함수 내부에 다른 함수가 정의된 경우, 내부 함수의 Environment Record 는
       상위 함수 실행 컨텍스트의 Environment Record 를 캡쳐하고, [[Outer Environment Reference]] 로 참조한다    이를 통해, 내부 함수는 상위함수의 Environment Record 에 접근 가능하다 (‼️클로저‼️)