실행 컨텍스트

se-een·2022년 9월 19일
0
post-thumbnail

이 글은 '이웅모'님의 '모던 자바스크립트 Deep Dive' 책을 통해 공부한 내용을 정리한 글입니다. 저작권 보호를 위해 책의 내용은 요약되었습니다.

소스코드 타입

소스코드 타입을 구분하는 이유는 소스코드 타입에 따라 실행 컨텍스트를 생성, 관리하는 과정이 다르기 때문이다.

  • 전역 코드 : 전역변수 관리 위한 전역 스코프 생성, var 전역변수와 함수 선언문의 전역 함수를 전역 객체의 프로퍼티와 메서드로 바인딩 및 전역 객체와 연결
  • 함수 코드 : 스코프 체인(전역 스코프에서 시작)의 일원으로 지역 스코프 생성, 지역변수, 매개변수, arguments 객체 관리
  • eval 코드 : strict mode에서 자신만의 독자적인 스코프 생성
  • 모듈 코드 : 독립적인 모듈 스코프 생성
var n;
n = 1;

자바스크립트 엔진은 위 코드를 두 과정으로 나누어서 처리한다.

  • 소스코드 평가 : 변수 식별자 n은 실행 컨텍스트가 관리하는 스코프로 등록 후 undefined로 초기화
  • 소스코드 실행 : 변수 식별자 n에 1의 값이 할당

실행 컨텍스트 (Execution Context)

실행 컨텍스트는 스코프, 호이스팅, 클로저, 이벤트 핸들러, 비동기 처리 방식 등의 동작 원리를 담고 있는 자바스크립트의 핵심 개념이다.

var age = 20;
const price = 100;

function f (x) {
  var age = 17;
  const price = 200;
  
  console.log(x + age + price);
}

f(23); // 240

위 예제를 통해 실행 컨텍스트의 동작과정을 알아보겠다.

전역 코드 평가

1. 전역 실행 컨텍스트 생성
실행 컨텍스트 스택에 전역 실행 컨텍스트를 생성한 뒤 푸시한다.

2. 전역 렉시컬 환경 생성
전역 렉시컬 환경을 생성하고 전역 실행 컨텍스트에 바인딩한다.

참고로 렉시컬 환경은 두 개의 컴포넌트로 이루어져있다.

  • 환경 레코드 (Environment Record) : 스코프에 포함된 식별자 등록 및 바인딩 된 값 관리
  • 외부 렉시컬 환경에 대한 참조 (Outer Lexical Environment Reference) : 상위 스코프 참조

3. 전역 환경 레코드 생성
전역 변수 및 전역 스코프, 전역 객체의 프로퍼티와 메서드, 표준 빌트인 객체를 제공한다. 이는 두 개의 항목으로 구성되어 있다. var 키워드로 선언된 변수와 함수 선언문으로 선언된 전역 함수, 빌트인 전역 프로퍼티와 전역 메서드, 표준 빌트인 객체는 객체 환경 레코드가 관리하고 let, const 키워드로 선언된 전역 변수는 선언적 환경 레코드가 관리한다.

4. 객체 환경 레코드 생성
BindingObject 객체(전역 객체)와 연결된다. 위에서 설명하였던 var 키워드 전역변수, 전역 함수는 BindingObject를 통해 전역 객체의 프로퍼티와 메서드가 된다. 따라서 위 코드의 var 전역 변수 age와 전역 함수 f는 객체 환경 레코드의 BindingObject에 바인딩되어 있는 전역 객체의 프로퍼티와 메서드가 된다. 또한 변수 age는 BindingObject를 통해 전역 객체에 키로 등록된 후 undefined가 할당되는 반면에 함수 f는 BindingObject를 통해 키로 등록된 후 생성된 함수 객체를 즉시 할당한다. 이것이 변수 호이스팅과 함수 호이스팅의 차이이다.

5. 선언적 환경 레코드 생성
let, const 키워드로 선언한 전역 변수가 등록되고 관리되는 곳이다. 변수 price는 const로 선언된 변수이며 전역 객체의 프로퍼티로서 참조할 수 없다. 또한 '선언 단계'와 '초기화 단계'가 분리되어 런타임에 '초기화 단계'가 진행되기 전까지 TDZ에 빠진다. 따라서 런타임 이전 price 변수에는 <uninitialized>가 할당된다. 이는 값이 아닌 표현이다.

6. this 바인딩
전역 환경 레코드의 [[GlobalThisValue]] 내부 슬롯에 this가 바인딩된다. 전역 코드에서 this는 전역 객체를 가리키므로 전역 객체(browser 환경 -> window)가 바인딩된다.

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

전역 코드 실행

전역 코드가 순차적으로 실행되면서 변수 할당문이 실행되어 전역 변수 age와 price에 값이 할당된다. 그리고 함수 f 또한 호출된다. 변수 할당문 및 함수 호출문을 실행하려면 어느 스코프의 어떤 식별자를 참조할지 정하는 과정을 수행한다. 이 과정을 식별자 결정이라 한다.

위 코드에선 현재 실행 중인 실행 컨텍스트는 전역 실행 컨텍스트이므로 전역 렉시컬 환경에서 식별자 age, price, f를 검색한 후 만약 현재 실행 컨텍스트에서 식별자를 찾을 수 없으면 외부 렉시컬 환경에 대한 참조가 가리키는 렉시컬 환경(상위 스코프)으로 이동하여 식별자를 검색한다. 이는 스코프 체인의 동작 원리이다. 위 코드의 경우 현재 실행 컨텍스트에서 찾고자 하는 식별자를 찾을 수 있으며 해당 변수에 값을 할당한다.

함수 f 코드 평가

1. 함수 실행 컨텍스트 생성
실행 컨텍스트 스택에 f 함수 실행 컨텍스트를 생성한 뒤 푸시한다. f 함수 실행 컨텍스트는 스택의 최상위로 실행 중인 실행 컨텍스트가 된다.

2. 함수 렉시컬 환경 생성
f 함수 렉시컬 환경을 생성하고 f 함수 실행 컨텍스트에 바인딩한다. 이 역시 환경 레코드와 외부 렉시컬 환경에 대한 참조로 구성된다.

3. 함수 환경 레코드 생성
함수 환경 레코든는 매개변수, arguments 객체, 함수 내부에서 선안한 지역변수 및 중첩 함수를 등록하고 관리한다.

4. this 바인딩
함수 환경 레코드의 [[ThisValue]] 내부 슬롯에 this가 바인딩된다. this는 동적 바인딩(호출 방식에 따른 바인딩)되는데 이 상황에선 전역 객체 window가 바인딩된다.

5. 외부 렉시컬 환경에 대한 참조 설정
f 함수 정의가 평가된 시점에 실행 중인 실행 컨텍스트의 렉시컬 환경의 참조가 할당된다. 즉, f 함수는 전역 코드에 정의된 함수이고 전역 코드 평가 시점에 평가되므로 전역 렉시컬 환경이 참조된다.

자바스크립트 엔진은 함수 객체를 생성할 때 현재 실행 중인 실행 컨텍스트의 렉시컬 환경(상위 스코프)을 함수 객체 내부 슬롯 [[Environment]]에 저장한다. 따라서 [[Environment]] 내부 슬롯이 렉시컬 스코프를 구현하는 메커니즘이다.

함수 f 코드 실행

런타임이 되어 함수 f의 소스코드가 순차적으로 실행되면서 매개변수 인수가 할당되고, 지역변수 age와 price에 값이 할당된다. 전역 코드 실행과 마찬가지로 현재 실행 컨텍스트의 렉시컬 환경에서 식별자를 검색하고 만약 식별자가 없을 경우 'OuterLexicalEnvironmentReference'가 가리키는 렉시컬 환경으로 이동하여 식별자를 검색한다. 위 상황은 함수 f의 렉시컬 환경에서 모든 식별자를 검색할 수 있다.

console.log

마지막으로 console.log를 통해 값을 출력하는 과정을 알아보겠다. 먼저 스코프 체인을 통하여 식별자 console에 대해 검색한다. console은 '전역 렉시컬 환경'의 '객체 환경 레코드'에서 BindingObject를 통해 검색이 가능하다.

console 객체에서 프로토타입 체인을 통해 log 메서드를 검색한다. console의 직접 소유하는 프로퍼티 log를 호출한다.

이후 각 식별자 x, age, price를 각각의 렉시컬 환경에서 검색하여 값을 출력한다.

함수 f 코드, 전역 코드 실행 종료

더 이상 실행할 코드가 없으면 종료된다. 이때 실행 컨텍스트 스택에서 f 함수 실행 컨텍스트가 pop되어 제거되고 실행 중인 실행 컨텍스트는 전역 컨텍스트가 된다. 전역 컨텍스트 역시 더는 실행할 코드가 없으므로 종료되고 스택에서 pop되어 스택은 비어있게 된다.

추후 클로저(Closure) 편에서 정리하겠지만 실행 컨텍스트가 소멸되었다 하더라도 다른 누군가가 해당 실행 컨텍스트의 렉시컬 환경을 참조하고 있다면 렉시컬 환경은 소멸되지 않는다. 즉, 다음과 같은 상황이 가능하다.

즉, 자바스크립트의 클로저는 실행 컨텍스트와 밀접한 관련이 있다.

profile
woowacourse 5th FE

0개의 댓글