실행 컨텍스트와 렉시컬 환경 심화 탐구

2024. 2. 9. 16:22자바스크립트

실행 컨텍스트는 소스코드를 실행하는 데 필요한 환경을 제공하고 코드의 실행 결과를 관리하는 영역이다.

그렇다면 어떤 환경을 제공하고, 어떻게 실행 결과를 관리할까?

 

우선 소스 코드는 평가와 실행의 과정을 거친다. 코드를 실행하기 이전에 어떤 변수가 있는지, 어떤 함수가 존재하는지 등 평가의 과정을 거친 후에 그 정보들로 실행 컨텍스트를 구성하고, 런타임에 코드가 실행된다.

const x = 1;

function foo() {
  const y = 2;

  function bar() {
    const z = 3;
    console.log(x + y + z);
  }
  bar();
}

foo();

 

실행 컨텍스트가 생성되고 콜 스택에 의해 관리되면서 코드의 제어권이 바뀌는 과정에 대해서는 생략한다.

복습할 때 스스로 다시 한 번 정리해보자.

나는 실행 컨텍스트가 생성될 때의 과정을 더 깊게 정리하고 싶다.

 

코드의 평가 과정을 거치면서 식별자를 실행 컨텍스트에 등록하게 된다. 이 때 등록하는 곳이 바로 렉시컬 환경이다. 

렉시컬 환경은 식별자만 등록하는 것이 아니라 외부 렉시컬 환경에 대한 참조 즉, 상위 스코프에 대한 정보도 등록할 수 있다. 이 부분은 후에 나올 클로저와 매우 밀접한 개념이다.

정리하면 콜 스택이 코드의 실행 순서를 관리한다면 렉시컬 환경은 스코프와 식별자를 관리한다.

렉시컬 환경은 식별자와 외부 렉시컬 환경에 대한 참조를 등록할 수 있다.

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

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️⃣ 환경레코드가 객체 환경 레코드와 선언적 환경 레코드로 나뉘어진 이유

 

렉시컬 환경은 환경 레코드와 외부 렉시컬 환경에 대한 참조 영역으로 나뉘어지는데 환경 레코드부터 살펴보자.

환경 레코드는 식별자에 대한 정보를 입력하는 곳이라고 간단하게 설명했다.

하지만 전역 실행 컨텍스트는 함수 실행 컨텍스트와 다른 점이 존재하는데 환경 레코드가 두 개로 나뉘어진다.

이것은 ES6부터 생긴 let, const 키워드로 선언한 변수들을 따로 관리하기 위해서이다.

전역 실행 컨텍스트에 등록되는 변수, 함수들은 모두 전역 객체의 프로퍼티로 등록이 되었다. 하지만 let과 const로 선언한 변수들은 전역 객체의 프로퍼티로 존재하지 않기 때문에 따로 선언적 환경 레코드라는 영역을 만들어 관리한다.

그렇다면 let과 const로 선언하지 않은 모든 변수 및 함수들은 그림과 같이 window 전역객체의 프로퍼티가 된다.

 

2️⃣ x는 undefined인데 y는 초기화조차 되지 않은 이유

x는 var로 선언했고 y는 const로 선언했다. 두 개의 차이점은 그림과 같이 코드가 평가되는 시점에서 var 키워드로 선언한 x는 선언과 동시에 undefined로 초기화가 된다. 따라서 다음과 같은 접근이 가능하다.

console.log(a);
var a = 2;

 

하지만 let과 const는 선언되는 시점에 초기화가 동시에 이뤄지지 않는다. 이것을 일시적 사각지대라고도 표현하는데 때문에 let과 const로 선언한 변수는 다음과 같은 접근이 이뤄지지 않는다.

console.log(a); // ❌
let a = 2;

 

let과 const가 호이스팅이 되지 않는 것이 아니다. let이든 const이든 var이든 변수의 선언부는 호이스팅이 된다.

 

3️⃣ 실행 컨텍스트와 호이스팅

런타임 이전에 자바스크립트 엔진이 코드를 실행하는데 필요한 정보를 이렇게 렉시컬 환경에 저장한다.

호이스팅은 실제 자바스크립트 엔진이 선언부를 끌어올리는 동작을 하는 것이 아니다. 코드 평가 과정에서 렉시컬 환경에 필요한 정보들이 미리 저장되었기에 가능한 원리이다.

 

4️⃣ this 바인딩

전역 실행 컨텍스트는 this에 window 객체가 바인딩이 된다. 이때 객체 환경 레코드와 선언적 환경 레코드에는 this 바인딩이 존재하지 않으며 오로지 환경 레코드에서만 this 바인딩이 존재한다.

 

그리고 전역 코드를 실행하면 다음과 같이 식별자에 값이 할당된다.

우선 해당 식별자에 값을 할당하기 위해서는 환경 레코드에 식별자가 등록되어있는지 확인한다.

등록되어 있으면 할당, 등록되어있지 않으면 외부 렉시컬 환경의 참조를 통해 외부 렉시컬 환경의 환경 레코드에서 검색한다. 즉, 상위 스코프로 이동하여 식별자를 검색하는 것이다.

쭉 실행을 하다가 foo(20)을 만났다. 이제 foo 함수가 호출된다.


코드의 제어권이 foo 함수로 넘어왔다.

먼저 foo 함수의 실행 컨텍스트가 생성되고 콜 스택에 푸쉬된다. 그런 다음 렉시컬 환경을 만든다.

렉시컬 환경 관련 부분만 그려보았다.

살펴볼 부분은 this 바인딩과 외부 렉시컬 환경의 참조 결정이다.

 

1️⃣ this에 바인딩된 객체가 window인 이유

this 바인딩은 함수의 호출 방식에 따라 결정된다. foo 함수는 일반함수로 호출되었기 때문에 this에 전역 객체가 바인딩 된 것이다.

 

2️⃣ 외부 렉시컬 환경의 참조가 전역 실행 컨텍스트인 이유

foo 함수 정의가 평가된 시점에 실행 중인 실행 컨텍스트는 전역 실행 컨텍스트이기 때문이다.

 

조금 더 자세히 설명하면 다음과 같다.

다시 전역 실행 컨텍스트 평가 시점이라고 생각하자. 환경 레코드에 기록할 데이터들을 쭉 찾다가 foo 함수를 발견한다. 이 때 foo 함수는 현재 실행중인 전역 실행 컨텍스트의 렉시컬 환경을 Environment라는 슬롯에 저장한다.

그리고 foo 함수의 실행 컨텍스트가 생성되고 평가 시점에 외부 렉시컬 환경에 대한 참조를 결정해야하는데 이 때 Environment 슬롯에 저장해두었던 값으로 결정하는 것이다.

때문에 외부 렉시컬 환경의 참조는 전역 실행 컨텍스트이다.


이번에는 코드의 제어권이 bar 함수로 넘어오고 bar 함수의 실행 컨텍스트가 생성된다. 

실행 컨텍스트의 생성 방식은 foo 함수와 똑같다.

 

이제 살펴볼 부분은 종료된 후의 과정이다.

bar 함수의 실행이 종료되면 콜 스택에서 bar 함수의 실행 컨텍스트가 팝되고 foo 함수의 실행 컨텍스트가 최상단에 위치하게 된다. bar 함수 실행을 위해 멈췄던 지점부터 다시 실행을 이어가게 된다.

 

주의할 부분은 실행 컨텍스트가 콜 스택에서 지워져도 렉시컬 환경은 즉시 소멸되는 것이 아니다. 렉시컬 환경을 외부 참조를 통해 다른 컨텍스트가 참조하고 있다면 GC의 대상이 아니기 때문에 소멸되지 않는다. 클로저가 가능한 이유이다.

'자바스크립트' 카테고리의 다른 글

호이스팅  (0) 2024.03.06
스코프  (0) 2024.03.03
이벤트 위임  (0) 2024.03.01
Ajax  (0) 2024.03.01
클로저 원리  (0) 2024.02.16