뚜벅

모던 자바스크립트 Deep Dive - 실행 컨텍스트 본문

JavaScript

모던 자바스크립트 Deep Dive - 실행 컨텍스트

초코맛젤리 2022. 12. 11. 16:49

실행 컨텍스트 

실행 컨텍스트를 알면 호이스팅과 클로저, 코드 실행 순서를 이해하는데 도움이 되기 때문에 정리합니다.

 

소스 코드의 타입 

- 전역 코드

전역 변수를 관리하기 위해 최상위 스코프인 전역 스코프를 생성한다.

 

- 함수 코드

지역 스코프를 생성하고 지역변수, 매개변수, arguments객체를 관리한다.

 

- eval 코드

엄격 모드에서 자신만의 독자적인 스코프를 생성한다. 

 

- 모듈 코드  

모듈별로 독립적인 모듈 스코프를 생성한다.

 

소스코드(전역, 함수, eval, 모듈) → 코드 평가 → 실행 컨텍스트(전역, 함수, eval, 모듈) 

소스 코드의 평가와 실행 

자바 스크립트 엔진은 소스코드를 2개의 과정으로 나누어 처리한다 ( 소스코드 평가, 소스코드 실행 )

 

- 소스 코드 평가

실행 컨텍스트 생성하고 변수, 함수 등의 선언문만 먼저 실행하여 스코프에 등록한다.

 

- 소스 코드의 실행 ( 런타임 )

소스 코드가 순차적으로 실행되며, 이때 필요한 정보는 실행 컨텍스트가 관리하는 스코프에서 검색해서 취득한다.

실행 이후 결과는 다시 실행 컨텍스트가 관리하는 스코프에 등록한다.

 

소스코드의 평가    실행 컨텍스트 생성    소스코드의 실행    실행 컨텍스트의 스코프에 등록

 

var x
x = 1
실행 컨텍스트
x undefined
 평가

실행 컨텍스트
x 1

실행

 

실행 컨텍스트의 역할 및 스택

소스코드를 실행하는데 필요한 환경을 제공하고 코드의 실행 결과를 실제로 관리한다. 

 

식별자를 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 메커니즘으로, 

모든 코드는 실행 컨텍스트를 통해 실행되고 관리된다.

 

식별자와 스코프는 실행 컨텍스트의 렉시컬 환경으로 관리한다.

코드 실행 순서는 실행 컨텍스트 스택으로 관리한다.

+ 최상위에 존재하는 실행 컨텍스트를 실행 중인 실행 컨텍스트라고 한다.

const x = 1

function foo(a){
    const x = 10
    const y = 20
    
    console.log(a + y)
}

foo(100)

console.log(x)

 

위의 코드의 실행 컨텍스트 순서를 예로 들면

1. 전역 코드 평가 (전역 실행 컨텍스트를 생성하고 실행 컨텍스트 스택에 푸시)

전역 변수와 전역 함수가 전역 스코프에 등록된다. 

이때, var로 선언한 전역 변수와 function으로 선언된 함수는 전역 객체의 프로퍼티와 메서드가 된다.

 

2. 전역 코드 실행 ( foo 함수가 호출되면서 실행 컨텍스트 스택에 foo 함수 실행 컨텍스트 푸시)

런타임이 시작되어 코드가 순차적으로 평가된다.

차례대로 전역 변수에 값이 할당되고 함수가 호출된다. ( 함수가 호출되면 함수 내부로 이동한다)

 

3. 함수 코드 평가 

이것도 앞의 과정과 똑같이 우선 평가를 먼저 한다. ( 매개변수, 지역변수 선언 및 지역 스코프에 등록)

 

4. 함수 코드 실행 ( foo 함수가 실행 종료되며 실행 컨텍스트 스택에서 pop 한다)

런타임이 시작되어 함수 코드가 순차적으로 실행된다. 

이때 식별자들에 값이 할당되고, 스코프 체인을 통해 식별자를 찾아 값을 출력한다.

 

5. 전역 코드 이어서 실행

마지막으로 console.log(x)가 실행되고 나서 실행 컨텍스트에서 전역 실행 컨텍스트를 pop 한다.

 

렉시컬 환경 

식별자와 식별자에 바인딩된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조로 실행 컨텍스트를 구성하는 컴포넌트다. 

( 키와 값을 갖는 객체 형태의 스코프를 생성한다.)

 

렉시컬 환경은 두 개의 컴포넌트로 구성된다.

 

1. 환경 레코드 (Environment Record)

스코프에 포함된 식별자 등록 및 식별자에 바인딩된 값을 관리하는 저장소

 

2. 외부 렉시컬 환경에 대한 참조 (Outer Lexical Environment Reference)

상위 스코프를 가리킨다.

 

실행 컨텍스트의 생성과 식별자 검색 과정 

var x = 1
const y = 2

function foo (a) {
    var x = 3
    const y = 4
    
    function bar(b) {
        const z = 5
        console.log(a + b + x + y + z)
    }
    bar(10)
}

foo(20)

위의 코드를 예시로 전체 실행 컨텍스트의 생성과 식별자 검색 과정을 살펴보겠습니다. 

 

1. 전역 객체 생성 

전역 코드 평가전 전역 객체가 먼저 생성된다.

 

2. 전역 코드 평가 

전역 코드 평과 과정은 다음과 같다. 

  1. 전역 실행 컨텍스트 생성 ( 실행 컨텍스트 스택에 push )
  2. 전역 렉시컬 환경 생성 ( 전역 렉시컬 환경을 생성하고 전역 실행 컨텍스트에 바인딩, 렉시컬 환경은 환경 레코드와 외부 렉시컬 환경에 대한 참조 2가지로 구성된다.)
    1. 전역 환경 레코드 생성 ( 객체 환경 레코드와 선언적 환경 레코드로 구성된다.
      1. 객체 환경 레코드 생성 ( var 키워드로 선언된 전역 변수 및 함수 선언문으로 정의된 전역 함수 등록)
      2. 선언적 환경 레코드 생성 ( let, const 키워드로 선언된 전역 변수 등록)
  3. this 바인딩 ( 함수의 호출 방식에 따라 다르다)
  4. 외부 렉시컬 환경에 대한 참조 결정 ( 상위 스코프 ) 

 

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

var 키워드로 선언한 전역 변수와 함수 선언문으로 정의한 전역 함수, 빌트인 전역 프로퍼티와 빌트인 전역 함수, 표준 빌트인 객체를 관리한다.

 

- BindingObject 

전역 객체 

 

- 선언적 레코드 (Declarative Environment Record) 

let, const 키워드로 선언한 전역 변수를 관리한다.

 

3. 전역 코드 실행 

 

4. foo 함수 코드 평가 ( 전역 코드 평과 과정과 동일 , 스택에 foo 실행 컨텍스트 추가 )

  1. 함수 실행 컨텍스트 생성
  2. 함수 렉시컬 환경 생성
    1. 함수 환경 레코드 생성 ( 매개변수, arguments 객체, 함수 내부의 지역 변수와 중첩 함수 등록 )
    2. this 바인딩
    3. 외부 렉시컬 환경에 대한 참조 결정

 

5. foo 함수 코드 실행 

 

6. bar 함수 코드 평가  ( foo 함수 코드 평가 과정과 동일 , 실행 컨텍스트 스택에 bar 실행 컨텍스트 추가 )

 

7. bar 함수 코드 실행  

 

9. bar 함수 코드 실행 종료 ( 스택에서 bar 실행 컨텍스트 제거 )

 

10. foo 함수 코드 실행 종료 ( 스택에서 foo 실행 컨텍스트 제거 ) 

 

11. 전역 코드 실행 종료 ( 스택에서 전역 실행 컨텍스트 제거 ) 

 

호이스팅

앞에서 정리한 실행 컨텍스트로 호이스팅를 설명하겠습니다.

 

- 호이스팅

 

호이스팅이란 선언문이 런타임 전에 스코프 최상단으로 끌어올려지는 것을 말한다.

이때 var 키워드와 let, const 키워드로 선언한 변수들의 호출 시점에 따른 값이 다른데

이것을 실행 컨텍스트 단계별로 살펴보며 이유를 알아보겠습니다. 

console.log(x) // undefined 
var x = 1 

console.log(x)

console.log(y) // 참조 에러
let y = 1 
console.log(y) // 1

1. 전역 코드 평가

var 키워드로 선언된 x는 평가 과정에서 선언과 초기화를 같이 진행합니다 (undefined)

let 키워드로 선언된 y는 평가 과정에서 선언만 진행합니다.

 

전역 실행 컨텍스트 
undefined 
y  

2. 전역 코드 실행

런타임이 시작되어 전역 코드가 순차적으로 실행됩니다.

1. console.log(x) 실행,  이때 현제 환경 레코드에서 식별자 x를 찾고 undefined를 출력한다.

2. x의 값에 1을 할당한다.

3. console.log(x) 실행, 현재 환경 레코드에서 식별자 x를 찾고 1을 출력한다.

4. console.log(y) 실행, 현재 환경 레코드에서 식별자 y를 찾지만 값이 없기 때문에 참조 에러 발생한다.

5. y의 값에 1을 할당한다.

6. console.log(y) 실행, 현재 환경 레코드에서 식별자 y를 찾고 1을 출력한다.

 

위의 과정을 통해 TDZ가 발생하는 이유와, var 키워드로 변수 선언 시 변수 선언 전 호출이 되는 이유를 알 수 있습니다.