[JS] 실행컨텍스트란?

ziwon.k·2021년 10월 3일
1

[JS] 자바스크립트

목록 보기
3/9
post-thumbnail

이전에 변수, 스코프 등을 배우면서 '실행 컨텍스트' 라는 용어를 접할 때 마다
아직 배우지는 않았지만 분명 중요한 개념이구나 생각했었다.
드디어 실행 컨텍스트를 배우고, 이해하게 되어 글로 정리해 보았다.

1. 소스코드 타입

  • ECMAScript 사양은 소스코드를 4가지 타입으로 분류하고 있다. 왜 4가지 타입으로 분류했을까?
    -> 4가지 타입의 소스코드만이 실행 컨텍스트를 생성하는데, 각 타입마다 실행 컨텍스트를 생성하는 과정과 관리하는 내용이 다르기 때문이다!
소스코드 타입설명생성 실행 컨텍스트
전역 코드전역에 존재하는 소스코드 (전역에 정의된 함수, 클래스 등의 내부 코드 포함X)
전역 코드는 전역 변수를 관리하기 위해 전역 스코프를 생성
var 키워드로 선언된 전역 변수와 함수 선언문으로 선언한 전역 함수를 전역 객체의 프로퍼티와 메서드로 바인딩하고 참조하기위해 전역 객체와 연결
전역 실행 컨텍스트
함수 코드함수 내부에 존재하는 소스코드(함수 내부에 중첩된 함수, 클래스 등의 내부 코드 포함X)
함수 코드는 지역 스코프를 생성하고 지역 변수, 매개변수, arguments 객체를 관리해야 함
생성한 지역 스코프를 전역 스코프에서 시작하는 스코프 체인의 일원으로 연결해야 함
함수 실행 컨텍스트
eval 코드빌트인 전역 함수인 eval 함수에 인수로 전달되어 실행되는 소스코드
eval 코드는 strict mode에서 자신만의 독자적인 스코프를 생성함
eval 실행 컨텍스트
모듈 코드모듈 내부에 존재하는 소스코드 (모듈 내부의 함수, 클래스 등의 내부 코드 포함X)
모듈 코드는 모듈별로 독립적이 모듈 스코프를 생성
모듈 실행 컨텍스트
  • 이처럼 각각의 소스코드 타입이 평가되는 과정에서 타입별로 다른 실행 컨텍스트를 생성하게 되는 것이다. 소스코드 평가와 실행과정에 대해 좀 더 알아보자.


2.소스코드 평가와 실행

처음에는 잘 이해되지 않았지만, 일단 전체적인 흐름을 파악한 뒤 넘어가면 도움이 될 것이다.

  • 모든 소스코드는 실행 전에 평가 과정을 거치며 실행을 위한 준비를 한다.
    -> 자바스크립트 엔진은 소스코드를 평가와 실행이라는 2개의 과정으로 나누어서 처리한다.
  1. 소스코드 평가 : 실행 컨텍스트를 생성하고 변수, 함수 등의 선언문만 먼저 실행하여 생성된 변수나 함수 식별자를 키로 실행 컨텍스트가 관리하는 스코프(렉시컬 환경의 환경 레코드)에 등록한다.
  1. 소스코드 실행 : 선언문을 제외한 소스코드가 순차적으로 실행되기 시작. 이때 실행에 필요한 변수, 함수의 참조를 실행 컨텍스트가 관리하는 스코프에서 검색해서 취득하게 된다. 또한 재할당, 값 변경 등 소스코드 실행 결과는 다시 실행 컨텍스트가 관리하는 스코프에 등록된다.

예시를 통해 좀 더 자세히 알아보자.

var x = 1;

한 줄의 코드지만 자바스크립트 엔진은 위 예제를 2개의과정으로 나누어서 처리한다.
먼저 소스코드 평가 과정에서 다음과 같이 선언문만 먼저 실행한다.

var x;

소스코드 평가를 통해 생성된 변수 식별자 x 는 실행 컨텍스트가 관리하는 스코프에 등록되고 undefined로 초기화 된다.

이후 평가 과정이 끝나면 소스코드 실행 과정이 시작된다. 이미 실행한 선언문을 제외한 코드를 실행하기 때문에 실행 과정에서는 변수 x에 1을 할당하는 할당문만 실행된다.

x = 1;

이때, x에 1을 할당하려면 x가 존재하는 변수인지, 즉 실행 컨텍스트가 관리하는 스코프에 등록된 변수인지 확인해야 한다. x가 등록된 변수라면 값을 할당하고, 할당 결과를 다시 실행 컨텍스트에게 알려 관리한다.



3. 실행 컨텍스트의 역할

그렇다면 실행 컨텍스트가 왜 필요할까?
우리가 코드를 실행하는 과정에서는 다음과 같은 관리가 필요하다.

  1. 선언에 의해 생성된 모든 식별자(변수, 함수 등)를 스코프를 구분해 등록하고 식별자의 변화를 지속적으로 관리해야 한다.
  2. 스코프 체인을 통해 상위 스코프로 이동하며 식별자를 검색할 수 있어야 한다.
  3. 현재 실행 중인 코드의 실행 순서를 변경할 수 있어야 하며, 다시 돌아갈 수 있어야 한다.

이러한 모든 것을 관리해주는 것이 바로 실행 컨텍스트다.
소스 코드 평가와 실행과정에서 확인했듯이

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

즉, 실행 컨텍스트는 식별자를 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 매커니즘으로, 모든 코드는 실행 컨텍스트를 통해 실행되고 관리된다.

실행 컨텍스트 구성
1. 렉시컬 환경 : 식별자와 스코프를 관리한다.
2. 실행 컨텍스트 스택 : 코드 실행 순서를 관리한다.



4. 실행 컨텍스트 스택

코드의 실행 순서를 관리하는 실행 컨텍스트 스택에 대해 알아보자.

var x = 1;

function outer () {
  const y = 2;

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

outer(); // 6

위 코드는 전역 코드와 함수 코드로 구성되어 있다.
자바스크립트 엔진은 먼저 전역 코드를 평가하여 전역 실행 컨텍스트를 생성한다. 이후 코드 실행 과정에서 함수가 호출되면 함수 코드를 평가해 함수 실행 컨텍스트를 생성한다.

이렇게 생성되는 실행 컨텍스트는 스택을 통해 관리된다.
코드가 실행되는 흐름에 따라 실행 컨텍스트 스택에 추가(push)되었다가 실행이 끝나면 제거(pop)되는 것이다.

위 코드의 평가, 실행 순서는 다음과 같다.

1. 전역 코드 평가와 실행

자바스크립트 엔진이 전역 코드를 평가해 전역 실행 컨텍스트를 생성하고 실행 컨텍스트 스택에 push 한다. 이때 전역 변수 x와 전역 함수 outer 는 전역 실행 컨텍스트에 등록된다. 이후 전역 코드가 실행될 때 x에 값이 할당되고, 함수 outer 가 호출된다.

2. outer 함수 코드 평가와 실행

전역 함수 outer가 호출되면 전역 코드 실행이 중단되고 outer 함수 내부로 이동한다. outer 함수 내부 코드를 평가하여 outer 함수 실행 컨텍스트를 생성하고 실행 컨텍스트 스택에 push 한다. 이때도 마찬가지로 지역 변수 y와 중첩 함수 inner가 outer 함수 실행 컨텍스트에 등록되고, outer 함수가 실행될 때 y에 값이 할당되고, inner 함수가 호출된다.

3. inner 함수 코드 평가와 실행

inner 함수가 호출되면 outer 함수 실행이 중단되고 inner 함수 내부로 이동한다. inner 함수 코드를 평가해 inner 함수 실행 컨텍스트를 생성하고 스택에 push한다. 지역변수 z가 bar 함수 실행 컨텍스트에 등록되고 코드가 실행될 때 값이 할당되고 console.log 메서드를 호출한 뒤 종료된다.

4. outer 함수 복귀

inner 함수가 종료되면 실행 컨텍스트 스택에서 pop되어 다시 outer 함수의 실행 컨텍스트가 최상위 컨텍스트가 된다. outer 함수도 실행이 끝나면 스택에서 pop되어 사라진다.

5. 전역 코드 복귀

outer 함수가 종료되어 전역 코드가 최상위 컨텍스트가 되었다. 마찬가지로 전역 코드의 실행이 끝난 후 스택에서 pop되고 스택에는 아무것도 남지 않게 된다.

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



5. 렉시컬 환경

그렇다면 이제 실행 컨텍스트에서 식별자와 스코프를 관리하는 렉시컬 환경에 대해 알아보자.

렉시컬 환경 : 식별자와 식별자에 바인딩 된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조

  • 렉시컬 환경은 키 - 값 을 갖는 객체 형태의 스코프를 생성하여 식별자를 키로 등록하고 실별자에 바인딩된 값을 관리한다.
    -> 렉시컬 환경은 스코프를 구분하여 식별자로 등록하고 관리하는 저장소 역할을 하는 렉시컬 스코프의 실체

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

환경 레코드 : 스코프에 포함된 식별자를 등록하고 등록된 식별자에 바인딩 된 값을 관리하는 저장소

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

예를들어, 위 예제에서 outer 렉시컬 환경은 outer 함수 소스코드를 평가하는 과정에서 생성되었고, outer 함수 소스코드는 전역 소스코드에 포함되어 있으므로 outer 함수의 상위 스코프는 전역 스코프가 된다. 또한 전역 소스코드의 경우 최상위 소스코드로 전역 소스코드를 포함하는 소스코드는 존재하지 않으므로 전역 렉시컬 환경의 외부 렉시컬 환경에 대한 참조는 null 이 된다.



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

아래 예제를 통해 본격적으로 소스코드 평가를 통해 실행 컨텍스트가 생성되는 과정과 소스코드가 실행되는 과정에서 식별자를 검색하는 과정을 알아보자.

var x = 1;
let y = 2;

function outer () {
  const w = 2;
  
  function inner () {
    const z = 3;
    console.log(x + y + z + w);
  }
  inner();
}

outer(); // 8

1. 전역 객체 생성

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

2. 전역코드 평가

소스코드가 로드되면 자바스크립트 엔진은 다음과 같은 순서로 전역 코드를 평가한다.

2.1 전역 실행 컨텍스트 생성

비어있는 전역 실행 컨텍스트를 생성하고 실행 컨텍스트 스택에 push 한다.
즉, 전역 실행 컨텍스트가 실행 중인 실행 컨텍스트가 된다.

2.2 전역 렉시컬 환경 생성

전역 렉시컬 환경을 생성하고, 전역 실행 컨텍스트와 연결(바인딩) 한다.

2.1 전역 환경 레코드 생성

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

모든 전역 변수가 전역 객체의 프로퍼티가 되는 ES6 이전에는 전역 객체가 전역 환경 레코드의 역할을 수행하였다. 하지만 ES6의 let, const 키워드로 선어한 전역 변수는 전역 객체의 프로퍼티가 되지 않고 별도의 개념적인 블록에 존재하게 된다. 이를 위해 var 키워드로 선언된 변수와 let, const 키워드로 선언된 변수는 전역 환경 레코드 내에서도 각각 객체 환경 레코드, 선언적 환경 레코드로 구분되어 관리된다.

객체 환경 레코드 : var 키워드로 선언한 변수, 함수 선언문으로 정의한 전역 함수, 빌트인 전역 프로퍼티와 빌트인 전역 함수, 표준 빌트인 객체 관리

선언적 환경 레코드 : let, const 키워드로 선언한 전역 변수 관리

2.1.1 객체 환경 레코드 생성

객체 환경 레코드는 BindingObject 라는 객체와 연결된다. (BindingObject는 전역 객체 생성 단계에서 생성된 전역 객체)
전역 코드 평가 과정에서 var 키워드로 선언한 전역 변수와 함수 선언문으로 정의한 전역 함수는 전역 환경 레코드의 객체 환경 레코드와 연결된 BindingObject를 통해 전역 객체의 프로퍼티와 메서드가 된다.
위 예제에서 전역 변수 x와 전역 함수 outrer 는 객체 환경 레코드를 통해 전역 객체의 프로퍼티와 메서드가 된다.

위 사진의 window는 전역 객체로 1. 전역 객체 생성 단계에서 생성된 것이다.
window에 등록된 x는 전역 코드 평가 시점에 전역 객체에 등록된 뒤, undefined가 암묵적으로 바인딩 되었다. 따라서 var 키워드로 선언한 변수는 코드 실행 단계에서 변수 선언문 이전에도 참조가 가능해지는 변수 호이스팅이 발생하게 되는 것이다.

함수 선언문으로 정의한 함수의 경우 평가되면 함수 이름과 동일한 이름의 식별자를 전역 객체에 키로 등록하고 생성된 함수 객체를 바인딩한다. 이 과정에서는 함수 호이스팅이 발생하므로 함수 선언문으로 정의한 함수는 코드 실행 시 함수 선언문에 도달하기 전에 함수를 호출할 수 있다. (현재까지 함수 선언문으로 정의된 함수는 함수 객체만 만들어진 상황으로, 함수 호출시 함수 코드 평가를 통해 함수 실행 컨텍스트가 생성된다.)

2.1.2 선언적 환경 레코드 생성

let, const 키워드로 선언한 전역 변수는 선언적 환경 레코드에 등록되고 관리된다.
위 예제의 y는 let 키워드로 선언한 변수이므로 전역 객체의 프로퍼티가 되지 않는다.
let, const 키워드로 선언한 변수들도 변수 호이스팅이 발생하는 것은 확실하다. 하지만 이들은 실행 단계에서 변수 선언문에 도달하기 전까지 참조할 수 없는 일시적 사각지대에 빠지기 때문에 참조할 수 없어 마치 변수 호이스팅이 발생하지 않는 것 처럼 동작한다.

2.2 this 바인딩

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

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

외부 렉시컬 환경에 대한 참조는 현재 평가중인 소스코드의 상위 스코프, 즉 현재 평가중인 소스코드를 포함하는 외부 소스코드의 렉시컬 환경을 말한다. 현재 평가 중인 소스코드는 전역 코드이며, 전역 코드를 포함하는 상위 소스코드는 없으므로 전역 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에는 null이 할당된다. 이말은 곧, 전역 렉시컬 환경이 스코프 체인의 종점이라는 뜻이기도 한다. 위에서 식별자와 스코프를 관리하는 것이 렉시컬 환경이라고 하였는데 그 렉시컬 환경들을 단방향으로 연결하여 스코프 체인을 구현하고, 스코프 체인을 통해 상위 스코프로 올라가면서 식별자 검색이 가능해지는 것이다.

3. 전역코드 실행

이제 선언문을 제외한 전역 코드가 순차적으로 실행된다. 이때 변수 할당문이 실행되어 변수에 값이 할당된다. 변수에 값을 할당하려면 먼저 해당 변수가 선언된 식별자인지 확인해야 한다. 또한 동일한 식별자가 여러 스코프에 존재할 경우 어떤 스코프의 식별자를 참조할 것인지 결정해야 한다. 이를 식별자 결정이라고 하는데, 식별자를 결정하기 위해서는 실행 중인 실행 컨텍스트에서 먼저 식별자를 검색한다. 만약 실행 중인 실행 컨텍스트에서 식별자를 검색할 수 없을 경우 외부 렉시컬 환경에 대한 참조를 통해 상위 렉시컬 환경, 즉 상위 스코프로 이동하며 식별자를 검색한다. 계속 이동하여 스코프 체인의 종점, 즉 전역 렉시컬 환경에 도달했을 때에도 식별자를 찾지 못한다면 더 이상 상위 스코프가 없으므로 참조 에러를 발생하게 된다.

4. outer 함수코드 평가

4.1 함수 실행 컨텍스트 생성

outer 함수 실행 컨텍스트를 생성한다. 생성된 함수 실행 컨텍스트는 함수 렉시컬 환경이 완성된 후 실행 컨텍스트 스택에 푸시된다. 이제 outer 함수 실행 컨텍스트가 실행 중인 실행 컨텍스트가된다.

4.2 함수 렉시컬 환경 생성

outer 함수 렉시컬 환경을 생성하고 outer 함수 실행 컨텍스트에 연결한다.

4.2.1 함수 환경 레코드 생성

함수 환경 레코드는 매개변수, arguments 객체, 함수 내부의 지역 변수와 중첩 함수를 등록, 관리한다.

4.2.2 this 바인딩

함수 환경 레코드의 [[ThisValue]] 내부 슬롯에 this가 바인딩 된다. 이때 [[ThisValue]] 내부 슬롯에 바인딩 될 객체는 함수 호출 방식에 따라 결정되는데, 현재 outer 함수는 일반 함수로 호출되었으므로 전역 객체가 바인딩된다.

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

외부 렉시컬 환경에 대한 참조에는 outer 함수 정의가 평가된 시점에 실행 중인 실행 컨텍스트의 렉시컬 환경의 참조가 할당되는데, outer 함수는 전역에 정의된 전역 함수다.
따라서 전역 코드가 평가될 때 평가되며 outer 함수의 외부 렉시컬 환경에 대한 참조에는 전역 렉시컬 환경의 참조가 할당된다.

5. outer 함수코드 실행

런타임이 시작되어 outer 함수가 호출되고 outer 함수의 소스코드가 순차적으로 실행된다. 매개변수에는 인수가 할당되고, 변수 할당문이 실행되어 지역 변수에 값이 할당된다. 그리고 inner 함수가 호출된다.

5번의 outer 함수 코드 실행 과정에서 inner 함수가 호출되어 inner 함수 평가, 실행 과정이 진행된다. 이후 inner 함수 실행이 종료되면 실행 컨텍스트 스택에서 pop되고, outer 함수 실행 컨텍스트, 전역 실행 컨텍스트도 순차적으로 pop되며 프로그램이 종료된다.


7. 마무리

실행 컨텍스트는 잘 이해해두면 스코프, 클로저와 같은 개념을 이해할 때도 도움이 되니 손으로 직접 그려가며 전체적인 흐름을 파악하는 것이 중요할 것 같다!


참고
모던자바스크립트 Deep Dive

profile
Frontend-Devloper

0개의 댓글