4 minute read

실행컨택스트

: 아래 블로그 내용을 모두 참고했다.

참고블로그 링크

실행컨택스트 종류

  • 전역 실행 컨텍스트(Global Execution Context)
    전역 코드가 실행될 때 최초 생성되는 실행 컨텍스트.
  • 함수 실행 컨텍스트(Function Execution Context)
    함수가 호출될 때마다 생성되는 실행 컨텍스트.
  • Eval 함수 실행 컨텍스트(Eval Function Execution Context)
    eval() 함수가 실행될 때 생성되는 실행 컨텍스트. 하지만, eval() 함수는 보안과 성능상의 이유로 사용을 비권장. 오래된 레거시에서만 가끔 볼 수 있는 코드이므로 제외함

기본 동작

전역 실행 컨택스트(Global Execution Context)함수 실행 컨택스트(Function Execution Context)실행 컨택스트 스택 이라는 자료구조에 저장되고 관리 된다.

  • 실행 컨택스트 스택은 호출스택(call stack) 이라고도 부른다.
  • ‘스택’ 이므로 후입선출 방식으로 컨택스트를 push / pop 한다.
    • Global Execution Context 는 스크립트 실행 시 가장 처음 생성되므로 항상 스택 가장 아랫부분에 psuh 된다.
    • 이후에 코드를 실행하면서 함수가 호출되면 해당 함수의 실행 컨택스트가 만들어지고 Global Execution Context 위로 push 된다.
<script>
    // 스크립트 실행 -> Global Execution Context 가 생성되고 스택에 push된다.
    
    function A(){
    	B(); // 함수B 의 실행 컨택스트가 생성되고 스택에 push.
    }
    
    function B(){
    	console.log('done!'); 
    }   
    
    A(); // 함수A 의 실행 컨택스트가 생성되고 스택에 push.
</script>

사진1

주의) 동일한 함수를 여러번 호출한다면?
실행 컨택스트는 하나만 만들어지는게 아닐까 하는 생각을 할 수 있는데 실행 컨택스트와 아무 상관이 없다. 반복해서 스택에 쌓일 뿐이다.

렉시컬 환경 (Lexical Environment)

사실 이 내용을 먼저 알아야 한다.
실행 컨택스트란 현재 실행중인 코드에 대한 세부 정보를 저장해놓은 내부 데이터 구조라 했었다. 이제 이 구조에 대해 살펴보려고 하는데, 그러기 위해선 렉시컬 환경 이라는 개념을 먼저 알고있어야 한다.

렉시컬 환경 구성

렉시컬 환경은 2개로 구성되어있다.

사진2

  1. 환경 레코드(Environment Record)
    현재 실행중인 코드 환경의 this값과 선언된 모든 변수와 함수가 저장되는 곳
  2. 외부 렉시컬 환경 (Outer Lexical Environment)
    외부 렉시컬 환경의 참조값 (외부 변수에 접근할 수 있게 된다)

기본동작

<script>
    // (A)

    let name1 = 'kwang'; // (B)
    var name2 = 'sunny';

    function test(){
        let msg = 'Hi~';
        console.log(`${msg} ${name1} ${name2}`); // (C)
    }
    
    test(); // (D)
</script>

렉시컬 환경은 실행 컨텍스트가 생성될 때 만들어지며, [환경 레코드]와 [외부 렉시컬 환경] 참조를 담은 바인딩 객체로 구성 된다.

모든 변수와 함수는 렉시컬 환경이 만들어질 때 환경 레코드에 저장되는 것이 아니라, 해당 변수나 함수가 선언되는 시점에 렉시컬 환경 내의 환경 레코드에 저장된다.

위 예시 코드에는 두 개의 렉시컬 환경이 존재한다.

사진3

  1. 전체 스크립트의 렉시컬 환경 (Global Lexical Environment)
  2. 함수test() 만의 렉시컬 환경

개념 관점에서 순서

  1. 실행중인 함수 내에서 어떤 변수가 나타나면 제일먼저 자신의 렉시컬 환경의 환경 레코드를 찾는다.
  2. 환경 레코드 안에 변수가 존재하지 않는다면, 외부 렉시컬 환경의 참조값을 통해 외부의 렉시컬 환경에서 해당 변수를 찾게되고 이 과정을 변수를 찾을때까지 반복한다.
  3. 만약 가장 바깥쪽 렉시컬 환경 (Global Lexical Environment) 에서도 이 변수를 찾지 못하면 Reference 에러가 발생한다.
  4. 전역 렉시컬 환경은 자신이 가장 바깥이기 때문에 외부 렉시컬 환경 값은 null 이다. (외부 렉시컬 환경에서 변수값을 찾는다? 클로져의 냄새가 나는데, 그렇다.. 이것은 또 클로져와 관련된 개념이다)

코드 관점에서 순서

  1. (A) 에서 전역 렉시컬 환경이 만들어진다. (변수나 함수들의 선언되는 시점에)
  2. (D) 에서 test 함수가 실행되고 test 함수의 렉시컬 환경이 만들어진다.
  3. (C) 부분에서 사용되는 변수들을 찾기위해 자바스크립트 엔진은 test의 렉시컬 환경의 환경 레코드를 뒤진다.
  4. msg 는 존재하지만, name1과 name2는 존재하지 않는다.
  5. Outer Lexical Environment 를 통해 외부 렉시컬 환경에 접근하여 name1, name2 를 찾는다

여기서 문제)
만약 (A) 에서 console.log(name1) 혹은 console.log(name2) 을 실행하면 출력결과는?

name1 출력 : reference 에러 발생! 
name2 출력 : undefined  

이전에 포스팅한 호이스팅을 알아야한다.
변수 및 함수 선언을 해당 스코프의 최상단으로 옮겨서 실행 컨텍스트생성되기 전에 먼저 호이스팅 처리한다.

변수나 함수가 선언되는 시점에 렉시컬 환경 내의 환경 레코드에 각각의 변수,함수값 들이 저장될때 호이스팅으로 인해 자바스크립트 엔진이 let name1 의 존재를 알고는 있다.

하지만, let은 (const도 동일) 변수가 선언되기 이전엔 uninitialized 라는 상태를 가지고 있어서 이때 접근하면 에러가 발생하게되고 변수가 선언된 이후부터 접근이 가능하다.

반면에 var로 선언된 변수는 렉시컬 환경에 올라가자마자 undefined로 초기화가 된다. 그래서 name2는 에러가 나지 않고 undefined 가 출력되는 것이다.

여기서 느낀점)
환경 레코드가 만들어지는 시점보다 호이스팅이 먼저 발생한다.

외부 렉시컬 환경 (Outer Lexical Environment) 정해지는 기준

“외부 렉시컬 환경은 함수가 실행되는 시점이 아닌 선언된 시점의 외부 환경을 가리킨다.”

let a = 'kim';

function fn1() {
  let a = 'sunny';
  fn2();
}

function fn2() {
  console.log(a); 
}

fn1(); // (가)

요약하면, 위 코드에서 fn2 의 [외부 렉시컬 환경]은 바로 [전역 렉시컬 환경] 이다.
즉, f1() 렉시컬환경이 [외부 렉시컬 환경]이 아니라는 뜻이다.

실행컨택스트 생성 과정

실행 컨택스트 두 단계를 거쳐 생성된다.

  1. 생성 단계 (Creation Phase)
    • 렉시컬 환경이 생성되고, this 바인딩이 이뤄진다.
    • 렉시컬 환경이의 환경 레코드에 변수와 함수가 저장된다.
    • 함수 선언문으로 선언된 함수는 바로 메모리에 올가게 되고, var 로 선언된 변수는 undefined 가 할당되고, let, const 로 할당된 변수는 uninitialized 상태이다.
    • Global 실행 컨택스트일 경우엔 window 에 전역 오브젝트 가 할당되고 this엔 window 가 할당된다.
    • Function 실행 컨택스트일 경우엔 window 할당은 없는 대신 argument 객체가 초기화 된다.
    • 이렇게 모든 변수가 생성 단계에서 렉시컬 환경에 초기화 되기 때문에 자바스크립트 엔진은 변수들의 존재를 모두 인지하게 되고, 이것이 호이스팅(hoisting) 이 발생하는 이유가 된다.

      (호이스팅을 다시 설명하자면 변수나 함수가 선언 전에 접근 가능한 현상으로, 그게 마치 선언된 변수들을 모두 최상단으로 끌어올리는것(hoisting) 처럼 보인다 하여 지어진 이름이다.)

  2. 실행 단계 (Execution Phase)
    • 생성 단계에서 결정된 렉시컬 환경을 가지고 있는 상태로, 코드를 한줄씩 실행해 내려간다.
    • 그 과정에서 변수에 값을 할당하거나 하면 렉시컬 환경의 해당 변수 값이 변경된다.

최종 정리

  1. 생성단계에서 렉시컬 환경을 구성한다. (변수나 함수가 선언되어있는 것을 보고 구성함)
    • [전역 렉시컬 환경]을 구성한다.
    • [외부 렉시컬 환경]을 구성한다.
    • 위 동작마다 [환경 레코드]를 구성한다 (선행으로 호이스팅 변수 개념 적용한 상태에서 구성함)
  2. 실행단계에서 실행컨택스트 구성 및 수행
    • [전역실행컨택스트] 를 필수로 구성한다.
    • [함수실행컨택스트] 를 구성한다.
    • 실행 컨텍스트가 구성되면 해당 컨텍스트에서 사용할 수 있는 [렉시컬 환경]을 참조한다.(아까 위에서 구성되어진.) 그래서 해당 환경에서 사용할 변수, 함수 등의 정보를 수집한다. 요약하면, 실행 컨텍스트가 구성된 이후에는 이미 해당 환경을 참조하고 있으므로, 렉시컬 환경을 참조한다.

Leave a comment