❓ 실행 컨텍스트(Execution Context)

코드를 실행하기 위해서는

  1. 선언에 의해 생성된 모든 식별자를 스코프를 구분하여 등록하고 상태 변화(식별자에 바인딩된 값의 변화)를 지속적으로 관리할 수 있어야 한다.

  2. 스코프는 중첩 관계에 의해 스코프 체인을 형성해야 한다. 즉, 스코프 체인을 통해 상위 스코프로 이동하며 식별자를 검색할 수 있어야 한다.

  3. 현재 실행 중인 코드의 실행 순서를 변경(ex 함수 호출)할 수 있어야 하며 다시 되돌아갈 수도 있어야 한다.

  • 이 모든 것을 관리하는 것이 바로 실행 컨텍스트다.
  • 즉, 코드 실행에 필요한 스코프, 식별자, 코드 실행 순서를 관리하기 위해서 실행 컨텍스트가 존재한다.
  • 실행 컨텍스트는 소스코드를 실행하는데 필요한 환경을 제공하고 코드의 실행 결과를 실제로 관리하는 영역이다.
  • 실행 컨텍스트는 식별자를 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 메커니즘으로, 모든 코드는 실행 컨텍스트를 통해 실행되고 관리된다.

📌 식별자와 스코프는 실행 컨텍스트의 렉시컬 환경으로 관리하고, 코드 실행 순서는 실행 컨텍스트 스택으로 관리한다.

✅ 소스코드의 타입

  • ECMAScript는 4가지 타입으로 소스코드를 구분한다.
  • 이 타입들의 소스코드는 실행 컨텍스트를 생성하고, 각 타입에 따라 실행 컨텍스트를 생성하는 과정과 관리 내용이 다르다.

🔰 전역 코드 (global code)

  • 전역에 존재하는 소스코드다. 단, 전역 함수와 클래스 등의 내부 코드는 포함되지 않는다.
  • 전역 변수를 관리하기 위해 전역 스코프를 생성한다. 전역 변수와 전역 함수를 전역 객체의 프로퍼티와 메소드로 바인딩하고 참조하기 위해 전역 객체와 연결되어야 한다.

🔰 함수 코드 (function code)

  • 함수 내부에 존재하는 소스코드다. 단, 함수 내부에 중첩된 함수나 클래스 등의 내부 코드는 포함되지 않는다.
  • 함수 코드는 지역 스코프를 생성하고, 매개변수, arguments 객체를 관리한다. 함수 코드가 평가되면 함수 실행 컨텍스트가 생성된다.

🔰 eval 코드 (eval code)

  • 빌트인 전역 함수인 eval 함수에 인수로 전달되어 실행되는 소스코드를 말한다.
  • eval 코드는 strict mode에서 자신만의 독자적인 스코프를 생성한다.

🔰 모듈 코드 (module code)

  • 모듈 내부에 존재하는 소스코드를 말한다. 단, 모듈 내부의 함수나 클래스 등의 내부 코드는 포함되지 않는다.
  • 모듈별로 독립적인 모듈 스코프를 생성한다.

✅ 소스코드의 평가와 실행

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

🔰 소스코드의 평가

  • 실행 컨텍스트를 생성하고 변수, 함수 등의 선언문만 먼저 실행하여 생성된 변수나 함수 식별자를 키로 실행 컨텍스트가 관리하는 스코프(렉시컬 환경의 환경 레코드)에 등록한다.

🔰 소스코드의 실행

  • 평가 과정이 끝나면 비로소 선언문을 제외한 소스코드가 순차적으로 실행되기 시작한다. 즉, 런타임이 시작된다. 이때 소스코드 실행에 필요한 정보를 스코프에서 검색해서 취득하고, 실행 컨텍스트가 관리하는 스코프에 등록한다.
	var x;
	x = 1;
  • 위 소스코드가 실행된다고 할 때,
  1. 소스코드 평가 과정에서 변수 선언문 var x;를 실행한다. 이때 생성된 변수 식별자 x는 >실행 컨텍스트가 관리하는 스코프에 등록되고 undefined로 초기화된다.
  2. 소스코드 평가 과정이 끝나면 변수 할당문 x = 1;이 실행된다. 이때 x변수에 값을 할당하기 위해 먼저 x 변수가 선언되었는지 확인해야 한다.
  3. 이를 위해 실행 컨텍스트가 관리하는 스코프에 x 변수가 등록되어 있는지 확인한다. x 변수가 선언된 변수라면 값을 할당하고 할당 결과를 실행 컨텍스트에 등록한다.

✅ 실행 컨텍스트 스택

  • 실행 컨텍스트 스택은 코드의 실행 순서를 관리한다.
  • 소스코드가 평가되면 실행 컨텍스트가 생성되고 실행 컨텍스트 스택의 최상위에 쌓인다.
  • 실행 컨텍스트 스택의 최상위에 존재하는 실행 컨텍스트는 언제나 현재 실행 중인 코드의 실행 컨텍스트다.
  • 따라서 실행 컨텍스트 스택의 최상위에 존재하는 실행 컨텍스트를 실행 중인 실행 컨텍스트라 부른다.

✅ 렉시컬 환경

  • 렉시컬 환경은 식별자와 식별자에 바인딩된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조로 실행 컨텍스트를 구성하는 컴포넌트다.
  • 렉시컬 환경은 스코프와 식별자를 관리한다.
  • 렉시컬 환경은 키와 값을 갖는 객체 형태의 스코프를 생성해 식별자를 키로 등록하고 식별자에 바인딩된 값을 관리한다.

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

🔰 환경 레코드

  • 스코프에 포함된 식별자를 등록하고 등록된 식별자에 바인딩된 값을 관리하는 저장소다.
  • 환경 레코드는 소스코드의 타입에 따라 관리하는 내용에 차이가 있다.

🔰 외부 렉시컬 환경에 대한 참조

  • 외부 렉시컬 환경에 대한 참조는 상위 스코프를 가리킨다. 이때 상위 스코프란 외부 렉시컬 환경, 즉 해당 실행 컨텍스트를 생성한 소스코드를 포함하는 상위 코드의 렉시컬 환경을 말한다.
  • 외부 렉시컬 환경에 대한 참조를 통해 단방향 링크드 리스트인 스코프 체인을 구현한다.

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

이제 실행 컨텍스트를 통해 실행 결과가 어떻게 관리되는지, 그리고 어떻게 식별자를 검색하는지 살펴보자.

var x = 1;
const y = 2;

function foo (a) {
  var x = 3;
  const y = 4;
  
  console.log(a + x + y);
}

foo(20); // 27

💠 전역 객체 생성

  • 전역 객체는 전역 코드가 평가되기 이전에 생성된다. 이때 전역 객체에는 빌트인 전역 프로퍼티와 빌트인 전역 함수, 그리고 표준 빌트인 객체가 추가되며 동작 환경 또는 특정 환경을 위한 호스트 객체를 포함한다.

💠전역 코드 평가

1. 전역 실행 컨텍스트 생성

  • 먼저 비어있는 전역 실행 컨텍스트를 생성하여 실행 컨텍스트 스택에 푸시한다.
  • 이때 전역 실행 컨텍스트는 실행 중인 실행 컨텍스트가 된다.

2. 전역 렉시컬 환경 생성

  • 전역 렉시컬 환경을 구성하는 컴포넌트인 전역 환경 레코드는 var 키워드와 let, const 키워드로 선언한 전역 변수를 구분하기 위해 객체 환경 레코드 (Object Environment Record)선언적 환경 레코드 (Declarative Environment Record) 로 구성되어 있다.
  • 전역 렉시컬 환경을 생성하고 전역 실행 컨텍스트에 바인딩한다.

2.1 전역 환경 레코드 생성

  • 전역 렉시컬 환경을 구성하는 컴포넌트인 전역 환경 레코드는 전역 변수를 관리하는 전역 스코프, 전역 객체의 빌트인 전역 프로퍼티와 빌트인 전역 함수, 표준 빌트인 객체를 제공한다.

2.1.1 객체 환경 레코드 생성

  • 기존의 전역 객체가 관리하던 var 키워드로 선언한 전역 변수와 함수 선언문으로 정의한 전역 함수, 빌트인 전역 프로퍼티와 빌트인 전역 함수, 표준 빌트인 겍체를 관리한다.
  • 전역 코드 평가 과정에서 전역 함수는 BindingObject를 통해 전역 객체의 프로퍼티와 메소드가 된다. -> 전역 객체를 가리키는 식별자 없이 객체의 프로퍼티 참조를 가능(window.alertalert로 참조할 수 있는 메커니즘)하게 한다.
var x = 1;
const y = 2;

function foo(a) {
 ...
}

위 코드에서 전역 변수 x와 전역 함수 foo가 BindingObject를 통해 전역 객체에 식별자를 키로 등록한다.

2.1.2 선언적 환경 레코드 생성

  • let, const 키워드로 선언한 전역 변수를 관리한다.
  • let 과 const 키워드로 선언한 전역 변수는 전역 객체의 프로퍼티가 되지 않고 개념적인 블록 내에 존재하게 된다고 했다. 여기서 개념적인 블록이 바로 전역 환경 레코드의 선언적 환경 레코드다.

2.2 this 바인딩

  • 전역 환경 레코드의 [[GlobalThisValue]] 내부에 this(전역 객체)가 바인딩된다.
    전역 환경 레코드를 구성하는 객체 환경 레코드와 선언적 환경 레코드에는 this 바인딩이 없다.
    📌 this 바인딩은 전역 환경 레코드와 함수 환경 레코드에만 존재한다.

2.3 외부 렉시컬 환경에 대한 참조 결정

  • 외부 렉시컬 환경에 대한 참조는 현재 평가 중인 소스코드를 포함하는 외부 소스코드의 렉시컬 환경, 즉 상위 스코프를 가리킨다. -> 전역 코드의 상위 스코프는 없으므로 null이 할당된다.
  • 이를 통해 단방향 링크드 리스트인 스코프 체인을 구현한다.

💠 전역 코드 실행

  • 전역 변수에 값이 할당되고, 전역 함수가 호출된다.
  • 할당과 호출을 위해 식별자 확인을 해야 한다.
  • 어느 스코프의 식별자를 참조해야 하는지 결정해야 한다. -> 이를 식별자 결정이라고 한다.
  • 📌식별자 결정을 위해 식별자를 검색할 때는 실행 중인 실행 컨텍스트에서 식별자를 검색하기 시작한다.

❓ 만약 실행 중인 실행 컨텍스트의 렉시컬 환경에서 식별자를 검색할 수 없다면?
✔️ 외부 렉시컬 환경에 대한 참조가 가리키는 렉시컬 환경, 즉 상위 스코프로 이동하여 식별자를 검색한다. 만약 전역 렉시컬 환경에서 식별자를 검색할 수 없다면 전역 렉시컬 환경은 스코프 체인의 종점이므로 참조 에러(ReferenceError)를 발생시킨다.

💠 함수 코드 평가

1. 함수 실행 컨텍스트 생성

  • 생성된 함수 실행 컨텍스트는 함수 렉시컬 환경이 완성된 다음 실행 컨텍스트 스택에 푸시된다.-> foo 함수 실행 컨텍스트가 실행 중인 컨텍스트가 된다.

2. 함수 렉시컬 환경 생성

  • 함수 렉시컬 환경을 생성하고 함수 실행 컨텍스트에 바인딩한다.

2.1 함수 환경 레코드 생성

  • 함수 환경 레코드는 매개변수, arguments 객체, 함수 내부에서 선언한 지역 변수와 중첩 함수를 등록하고 관리한다.

2.2 this 바인딩

  • 함수 환경 레코드의 [[ThisValue]] 내부 슬롯에 this가 바인딩된다.

2.3 외부 렉시컬 환경에 대한 참조 결정

  • 외부 렉시컬 환경에 대한 참조에 함수 정의가 평가된 시점에 실행 중인 실행 컨텍스트의 렉시컬 환경의 참조가 할당된다.
    📌 함수 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 할당되는 것은 바로 함수의 상위 스코프를 가리키는 함수 객체의 내부 슬롯 [[Environment]]에 저장된 렉시컬 환경의 참조다.

💠 함수 코드 실행

  • 매개변수에 인수가 할당되고, 변수 할당문이 실행되고 , 내부함수가 호출된다.
  • 이때 식별자 결정을 위해 실행 중인 실행 컨텍스트의 렉시컬 환경에서 식별자를 검색하기 시
    작한다.
    검색된 식별자에 값을 바인딩한다.

<함수 코드가 평가되어 실행된 상태>

  • 그리고 console.log(a + b + x + y + z); 가 실행된다.

1. console.log 식별자 검색

  • 먼저 console 식별자를 찾기 위해 상위 스코프를 따라가며 검색한다. -> console 식별자는 전역 렉시컬 환경에서 찾을 수 있다.

2. log 메소드 검색

  • 이제 console 식별자에 바인딩된 객체인 log 메서드를 검색한다. 이때 console 객체의 프로토타입 체인을 통해 메서드를 검색한다. log 메서드는 console 객체가 가진 프로퍼티다.

3. 표현식 a+b+x+y+z의 평가

  • 이제 console.log 메서드에 전달된 a + x + y를 평가하기 위해 각각의 식별자를 스코프 체인을 통해 검색한다.

4. console.log 메소드 호출

  • a, x, y 식별자는 foo 함수 렉시컬 환경에서 검색된다. 이제 표현식 a + x + y 를 평가해 값을 console.log 메서드에 전달해 호출한다.

💠 함수 코드 실행 종료

  • 실행 컨텍스트 스택에서 함수 실행 컨텍스트가 pop되어 제거되고 외부 함수가 존재한다면 외부 함수의 실행 컨텍스트가 실행 중인 실행 컨텍스트가 된다.
    ❗ 실행 컨텍스트 스택에서 함수 실행 컨텍스트가 제거되었다고 해서 함수 렉시컬 환경까지 즉시 소멸하는 것은 아니다. 누군가에 의해 참조되지 않을 때 카비지 컬렉터의 대상이 된다. 렉시컬 환경은 실행 컨텍스트에 의해 참조되기는 하지만 독립적인 객체다.

💠 전역 코드 실행 종료

  • 모든 함수가 종료되면 더는 실행할 전역 코드가 없으므로 전역 코드의 실행이 종료되고 전역 실행 컨텍스트도 실행 컨텍스트 스택에서 pop 되어 실행 컨텍스트 스택에는 아무것도 남아있지 않게 된다.

✅ 실행 컨텍스트와 블록 레벨 스코프

  • let, const 포스트에서 봤듯이, let, const로 선언한 변수는 블록 레벨 스코프를 따른다.
let x = 1;

if(true) {
  let x = 10;
  console.log(x); // 10
}

console.log(x); // 1
  • if 문의 코드 블록이 실행되면 if 문의 코드 블록을 위한 블록 레벨 스코프를 생성한다
  • 선언적 환경 레코드를 갖는 렉시컬 환경을 생성해 기존의 전역 렉시컬 환경을 교체한다.

  • if 문 코드 블록의 실행이 종료되면 if 문의 코드 블록이 실행되기 이전의 렉시컬 환경으로 되돌린다. 이는 if 문 뿐만 아니라 블록 레벨 스코프를 생성하는 모든 블록문에 적용된다.
  • for문의 코드 블록이 반복해서 실행될 때마다 독립적인 렉시컬 환경을 생성하여 식별자의 값을 유지한다.

<모던 자바스크립트 deepdive와, 추가 탐구한 내용을 정리한 포스트입니다.>

profile
wanna be bright person✨ Front-End developer

0개의 댓글